From 646f5b65e8195de534f4ead1a90c79cf3d99a855 Mon Sep 17 00:00:00 2001 From: Udit Samani Date: Wed, 16 Apr 2025 09:03:20 +0100 Subject: [PATCH 01/14] Refactor of DCAP-RS to make it more modular (#15) * feat: make dcap-rs more modular * feat: implement solana based dcap-rs sdk * fix: add missing tdxtcbcomponents check * feat: add borsh serialization and deserialization for TcbInfo * feat: add borsh serialization and deserialization for EnclaveIdentity --- .github/pull_request_template.md | 15 + .github/workflows/main.yaml | 29 + .github/workflows/pr.yaml | 40 ++ Cargo.toml | 20 +- Makefile | 26 + data/full_collateral_sgx.json | 1 + data/qeidentityv2.json | 61 +- data/qeidentityv2_apiv4.json | 2 +- data/quote_sgx.bin | Bin 0 -> 4600 bytes ...ote_tdx_00806f050000.dat => quote_tdx.bin} | Bin data/sgx_x509_extension.der | Bin 0 -> 556 bytes data/signing_cert.pem | 16 + data/tcb_info_v2.json | 221 ++++++ data/tcb_info_v3_with_tdx_module.json | 1 + data/tcbinfov3_00806f050000.json | 1 - help.sh | 70 ++ rustfmt.toml | 12 + src/constants.rs | 15 - src/lib.rs | 524 +++++++++++--- src/trust_store.rs | 189 +++++ src/types/cert.rs | 120 ---- src/types/collateral.rs | 50 ++ src/types/collaterals.rs | 349 ---------- src/types/enclave_identity.rs | 430 +++++++----- src/types/mod.rs | 132 +--- src/types/quote/body.rs | 35 + src/types/quote/cert_data.rs | 117 ++++ src/types/quote/header.rs | 87 +++ src/types/quote/mod.rs | 71 ++ src/types/quote/signature.rs | 169 +++++ src/types/quotes/body.rs | 253 ------- src/types/quotes/mod.rs | 183 ----- src/types/quotes/version_3.rs | 77 -- src/types/quotes/version_4.rs | 79 --- src/types/report.rs | 169 +++++ src/types/sgx_x509.rs | 466 +++++++++++++ src/types/tcb_info.rs | 585 ++++++++++++++++ src/types/tcbinfo.rs | 655 ------------------ src/utils.rs | 307 ++++++++ src/utils/cert.rs | 624 ----------------- src/utils/crypto.rs | 19 - src/utils/enclave_identity.rs | 124 ---- src/utils/hash.rs | 20 - src/utils/mod.rs | 7 - src/utils/quotes/mod.rs | 300 -------- src/utils/quotes/version_3.rs | 52 -- src/utils/quotes/version_4.rs | 113 --- src/utils/tcbinfo.rs | 130 ---- src/utils/tdx_module.rs | 84 --- 49 files changed, 3447 insertions(+), 3603 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/main.yaml create mode 100644 .github/workflows/pr.yaml create mode 100644 Makefile create mode 100644 data/full_collateral_sgx.json create mode 100644 data/quote_sgx.bin rename data/{quote_tdx_00806f050000.dat => quote_tdx.bin} (100%) create mode 100644 data/sgx_x509_extension.der create mode 100644 data/tcb_info_v2.json create mode 100644 data/tcb_info_v3_with_tdx_module.json delete mode 100644 data/tcbinfov3_00806f050000.json create mode 100755 help.sh create mode 100644 rustfmt.toml delete mode 100644 src/constants.rs create mode 100644 src/trust_store.rs delete mode 100644 src/types/cert.rs create mode 100644 src/types/collateral.rs delete mode 100644 src/types/collaterals.rs create mode 100644 src/types/quote/body.rs create mode 100644 src/types/quote/cert_data.rs create mode 100644 src/types/quote/header.rs create mode 100644 src/types/quote/mod.rs create mode 100644 src/types/quote/signature.rs delete mode 100644 src/types/quotes/body.rs delete mode 100644 src/types/quotes/mod.rs delete mode 100644 src/types/quotes/version_3.rs delete mode 100644 src/types/quotes/version_4.rs create mode 100644 src/types/report.rs create mode 100644 src/types/sgx_x509.rs create mode 100644 src/types/tcb_info.rs delete mode 100644 src/types/tcbinfo.rs create mode 100644 src/utils.rs delete mode 100644 src/utils/cert.rs delete mode 100644 src/utils/crypto.rs delete mode 100644 src/utils/enclave_identity.rs delete mode 100644 src/utils/hash.rs delete mode 100644 src/utils/mod.rs delete mode 100644 src/utils/quotes/mod.rs delete mode 100644 src/utils/quotes/version_3.rs delete mode 100644 src/utils/quotes/version_4.rs delete mode 100644 src/utils/tcbinfo.rs delete mode 100644 src/utils/tdx_module.rs diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..4b9377a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## 📝 Summary + + + +## 💡 Motivation and Context + + + +--- + +## ✅ I have completed the following steps: + +* [ ] Run `make lint` +* [ ] Run `make test` +* [ ] Added tests (if applicable) \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..86255eb --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,29 @@ +name: Main Branch CI + +on: + push: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Run lint + run: make lint + + - name: Run tests + run: make test + + - name: Build + run: make build diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..af090fd --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,40 @@ +name: Pull Request + +on: + pull_request: + branches: [ main ] + + +env: + CARGO_TERM_COLOR: always + + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Run Lint + run: make lint + + - name: Run tests + run: make test + + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build + run: make build \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 3813453..257ecb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,17 +13,27 @@ keywords = ["dcap", "intel", "quote", "attestation", "sgx", "rust", "library"] crate-type = ["lib"] [dependencies] -hex = { version = "0.4" } -x509-parser = { version = "0.15.1" } +hex = { version = "0.4.3", features = ["serde"]} +anyhow = { version = "1.0.76" } +x509-cert = { version = "0.2.5" } +asn1 = { version = "0.17.0" } serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } -chrono = { version = "0.4" } +serde_json = { version = "1.0.125", features = ["raw_value"] } +chrono = { version = "0.4.38", features = ["serde"]} time = { version = "0.3.36" } +zerocopy = { version = "0.7.34", features = ["derive"]} +pem = { version = "3.0.4" } +base64ct = { version = "=1.6.0" } p256 = { version = "0.13.2" } sha2 = { version = "0.10.8" } sha3 = { version = "0.10.8" } -alloy-sol-types = { version = "0.8.12" } +borsh = { version = "1.5.7", features = ["derive"]} + +[dependencies.x509-verify] +version = "0.4.6" +default-features = false +features = [ "std", "p256", "x509" ] [features] default = [] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3508ea9 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +.DEFAULT_GOAL := help +TAG ?= $(shell git rev-parse --short HEAD) + + +.PHONY: help +help: ## Display this help message + @./help.sh "$(MAKEFILE_LIST)" + + +.PHONY: lint +lint: ## Run Linting Checks + @cargo clippy -- -D warnings + + +.PHONY: fmt +fmt: ## Format the code + @cargo fmt + +.PHONY: test +test: ## Run the tests + @cargo test + +.PHONY: build +build: ## Build the project + @cargo build + @cargo test \ No newline at end of file diff --git a/data/full_collateral_sgx.json b/data/full_collateral_sgx.json new file mode 100644 index 0000000..71c08b9 --- /dev/null +++ b/data/full_collateral_sgx.json @@ -0,0 +1 @@ +{"version":3,"root_ca_crl":"-----BEGIN X509 CRL-----\r\nMIIBIjCByAIBATAKBggqhkjOPQQDAjBoMRowGAYDVQQDDBFJbnRlbCBTR1ggUm9v\r\ndCBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRh\r\nIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMXDTI0MDMyMDE5MTkzMFoX\r\nDTI1MDQwMzE5MTkzMFqgLzAtMAoGA1UdFAQDAgEBMB8GA1UdIwQYMBaAFCJlDNZa\r\nnTSJ84O0lVK/UBs5JwasMAoGCCqGSM49BAMCA0kAMEYCIQDnYG/vLaaHhaDDm8NK\r\nw0TJ4tbtSwIj55psYpfUIbc3hAIhAPwVh67OQpbV6TcP1qREpy0DxZjLIdyBBMVb\r\nEnt2bqgr\r\n-----END X509 CRL-----\r\n","pck_crl":"-----BEGIN X509 CRL-----\r\nMIIBKzCB0QIBATAKBggqhkjOPQQDAjBxMSMwIQYDVQQDDBpJbnRlbCBTR1ggUENL\r\nIFByb2Nlc3NvciBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNV\r\nBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMXDTI0MDgy\r\nNjAwMzUwMFoXDTI0MDkyNTAwMzUwMFqgLzAtMAoGA1UdFAQDAgEBMB8GA1UdIwQY\r\nMBaAFNDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMCA0kAMEYCIQCdPOE4\r\nxy6tc43obNZZHCMI1npU0yufdw+EuEFAc2kYmAIhAIP75jzqhw63aAqD4nDpRLJK\r\nG6FN/N+QeoW37lQgn+rc\r\n-----END X509 CRL-----\r\n","tcb_info_and_qe_identity_issuer_chain":"-----BEGIN CERTIFICATE-----\nMIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0cAMEQCIB9C8wOAN/ImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj\nftbrNGsGU8YH211dRiYNoPPu19Zp/ze8JmhujB0oBw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n","pck_crl_issuer_chain":"-----BEGIN CERTIFICATE-----\nMIICmDCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC\nMGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD\nb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw\nCQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHExIzAh\nBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl\nbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB\nMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg\ntdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i\nHBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww\nUgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl\ncnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFNDo\nqtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEAMAoGCCqGSM49BAMCA0gAMEUCIQCJgTbtVqOyZ1m3jqiAXM6QYa6r5sWS\n4y/G7y8uIJGxdwIgRqPvBSKzzQagBLQq5s5A70pdoiaRJ8z/0uDz4NgV91k=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n","qe_identity_issuer_chain":"-----BEGIN CERTIFICATE-----\nMIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG\nA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw\nb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD\nVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv\nP+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju\nypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f\nBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz\nLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK\nQEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG\nSM49BAMCA0cAMEQCIB9C8wOAN/ImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj\nftbrNGsGU8YH211dRiYNoPPu19Zp/ze8JmhujB0oBw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n","platform_ca_crl":"","processor_ca_crl":"-----BEGIN X509 CRL-----\nMIIBKjCB0QIBATAKBggqhkjOPQQDAjBxMSMwIQYDVQQDDBpJbnRlbCBTR1ggUENL\nIFByb2Nlc3NvciBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNV\nBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMXDTI0MDUw\nOTA5NDY0MFoXDTI0MDYwODA5NDY0MFqgLzAtMAoGA1UdFAQDAgEBMB8GA1UdIwQY\nMBaAFNDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMCA0gAMEUCIQD6Taqf\nC/jdF7CV8SieNuTR0a2c2MHvVXIeuIQkXdjHOAIgP1yjkl8vtmHoCtYNoM/q4fSL\nPiX+8tFyGIAGM785ooA=\n-----END X509 CRL-----","tcb_info":{"tcbInfo":{"id":"SGX","version":3,"issueDate":"2024-08-26T00:15:38Z","nextUpdate":"2024-09-25T00:15:38Z","fmspc":"00A067110000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":16,"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":11},{"svn":11},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":12},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"SWHardeningNeeded","advisoryIDs":["INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":11},{"svn":11},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"ConfigurationAndSWHardeningNeeded","advisoryIDs":["INTEL-SA-00289","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":10},{"svn":10},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":12},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00828","INTEL-SA-00289","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":10},{"svn":10},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"OutOfDateConfigurationNeeded","advisoryIDs":["INTEL-SA-00289","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":9},{"svn":9},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":12},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2022-11-09T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00289","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":9},{"svn":9},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13},"tcbDate":"2022-11-09T00:00:00Z","tcbStatus":"OutOfDateConfigurationNeeded","advisoryIDs":["INTEL-SA-00289","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":5},{"svn":5},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":4},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11},"tcbDate":"2021-11-10T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00614","INTEL-SA-00617","INTEL-SA-00289","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":5},{"svn":5},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":4},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":10},"tcbDate":"2020-11-11T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00289","INTEL-SA-00614","INTEL-SA-00617","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":5},{"svn":5},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11},"tcbDate":"2021-11-10T00:00:00Z","tcbStatus":"OutOfDateConfigurationNeeded","advisoryIDs":["INTEL-SA-00289","INTEL-SA-00614","INTEL-SA-00617","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":5},{"svn":5},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":10},"tcbDate":"2020-11-11T00:00:00Z","tcbStatus":"OutOfDateConfigurationNeeded","advisoryIDs":["INTEL-SA-00477","INTEL-SA-00289","INTEL-SA-00614","INTEL-SA-00617","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]},{"tcb":{"sgxtcbcomponents":[{"svn":5},{"svn":5},{"svn":2},{"svn":2},{"svn":255},{"svn":1},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00289","INTEL-SA-00614","INTEL-SA-00617","INTEL-SA-00657","INTEL-SA-00767","INTEL-SA-00828","INTEL-SA-00615"]}]},"signature":"e20f8a8172235f2f3bf088617fb4f107d6fc925c519cedb9cc7f69ea971390d7e9e880c5298c9c0a3194d511d8b772aa3764d61751aae409cf9ee43489e49a37"},"qe_identity":{"enclaveIdentity":{"id":"QE","version":2,"issueDate":"2024-08-26T00:31:38Z","nextUpdate":"2024-09-25T00:31:38Z","tcbEvaluationDataNumber":16,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF","isvprodid":1,"tcbLevels":[{"tcb":{"isvsvn":8},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":6},"tcbDate":"2021-11-10T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00615"]},{"tcb":{"isvsvn":5},"tcbDate":"2020-11-11T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00477","INTEL-SA-00615"]},{"tcb":{"isvsvn":4},"tcbDate":"2019-11-13T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00334","INTEL-SA-00477","INTEL-SA-00615"]},{"tcb":{"isvsvn":2},"tcbDate":"2019-05-15T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00219","INTEL-SA-00293","INTEL-SA-00334","INTEL-SA-00477","INTEL-SA-00615"]},{"tcb":{"isvsvn":1},"tcbDate":"2018-08-15T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00202","INTEL-SA-00219","INTEL-SA-00293","INTEL-SA-00334","INTEL-SA-00477","INTEL-SA-00615"]}]},"signature":"74e073de47260ed5b27aceb5a114b755269699b3281f42a44aa5ac19c4286df1d23e1a402324cf29c7316a0e0ea91e01111979baa94249790133332770520861"}} diff --git a/data/qeidentityv2.json b/data/qeidentityv2.json index 2f8cd0b..1126029 100644 --- a/data/qeidentityv2.json +++ b/data/qeidentityv2.json @@ -1 +1,60 @@ -{"enclaveIdentity":{"id":"QE","version":2,"issueDate":"2025-02-13T03:33:12Z","nextUpdate":"2025-03-15T03:33:12Z","tcbEvaluationDataNumber":17,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF","isvprodid":1,"tcbLevels":[{"tcb":{"isvsvn":8},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":6},"tcbDate":"2021-11-10T00:00:00Z","tcbStatus":"OutOfDate"},{"tcb":{"isvsvn":5},"tcbDate":"2020-11-11T00:00:00Z","tcbStatus":"OutOfDate"},{"tcb":{"isvsvn":4},"tcbDate":"2019-11-13T00:00:00Z","tcbStatus":"OutOfDate"},{"tcb":{"isvsvn":2},"tcbDate":"2019-05-15T00:00:00Z","tcbStatus":"OutOfDate"},{"tcb":{"isvsvn":1},"tcbDate":"2018-08-15T00:00:00Z","tcbStatus":"OutOfDate"}]},"signature":"7cdd92c48255c1006054e1b821aafd6e497f5b9e42c5f6e922ae4002b3f57ab8cf4dc8c865a6f0c322efc6bc1db4c1eb2eb88f3370baac091c540c3e5166458c"} \ No newline at end of file +{ + "enclaveIdentity": { + "id": "QE", + "version": 2, + "issueDate": "2024-09-10T06:23:23Z", + "nextUpdate": "2024-10-10T06:23:23Z", + "tcbEvaluationDataNumber": 16, + "miscselect": "00000000", + "miscselectMask": "FFFFFFFF", + "attributes": "11000000000000000000000000000000", + "attributesMask": "FBFFFFFFFFFFFFFF0000000000000000", + "mrsigner": "8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF", + "isvprodid": 1, + "tcbLevels": [ + { + "tcb": { + "isvsvn": 8 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "UpToDate" + }, + { + "tcb": { + "isvsvn": 6 + }, + "tcbDate": "2021-11-10T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "isvsvn": 5 + }, + "tcbDate": "2020-11-11T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "isvsvn": 4 + }, + "tcbDate": "2019-11-13T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "isvsvn": 2 + }, + "tcbDate": "2019-05-15T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "isvsvn": 1 + }, + "tcbDate": "2018-08-15T00:00:00Z", + "tcbStatus": "OutOfDate" + } + ] + }, + "signature": "a155e5ebd26d374a29296f392836acd5fc0274d9a48da6c66686dc3884af8ef6882623fbf3f79bd057ae22d5d1eb8d3d90c0c9883b840ec1066a47937357ef78" +} diff --git a/data/qeidentityv2_apiv4.json b/data/qeidentityv2_apiv4.json index e01d1f5..e1d72c4 100644 --- a/data/qeidentityv2_apiv4.json +++ b/data/qeidentityv2_apiv4.json @@ -1 +1 @@ -{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2025-02-13T03:39:00Z","nextUpdate":"2025-03-15T03:39:00Z","tcbEvaluationDataNumber":17,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"806c264115a883173594035b667358449d2278e95e865f97e51f39b63cf1ab41fb052859ae37d169ef4e4f9d09ddbf60f26cebb6012eed689eeae2c5230d7a22"} \ No newline at end of file +{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2024-09-10T06:27:30Z","nextUpdate":"2024-10-10T06:27:30Z","tcbEvaluationDataNumber":16,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"4994904c51f5a425e32ec0e8a79e8ae4b5510fe462b262a21c34e50d241a621f6cb7ebabb7876a3b97b11a34c3ca41c32deb25e3b1d0e38581477c177013e652"} diff --git a/data/quote_sgx.bin b/data/quote_sgx.bin new file mode 100644 index 0000000000000000000000000000000000000000..2a1371f84f0eec0818720a4e1801bfd85ee39d8e GIT binary patch literal 4600 zcmd59WUG+cg)>!C-2Ac2`Zs3oxZl) zZKoYD2!;R$MC5VA6Yv2T&wz-B<{~PPa41IL54nKy3MkxRfQZo$R9Ju0-95_gEyv~l zagwH!&+j+C`My7&Srs@s;9pc=Q{bi@Gm&?0%j~?F+PLeM%Z^!VZ{6_gySIE{$HZ%o zd};4#@9p*Xt>19;sdICy{o9XqtzFnPH~*TzowqBWbsm3z+n>PI(u1!w&wOikU0`y} zfA<&HOK04=Z}kI*I&{a}Q$OK$r{4I?eOveke)bLJtSygz>fP8CyDG=L`f28w>rX$_ z^Z)1Ts~&###%q5w_T{%^<<39M1%LX3v$Eg5_x{Ic-hOmYKJWaCH_fq|1A$vmb>~gD z-u=3o{$9NM%ctIW;+Xo4>Gh?@ZrpvZdCK-5pYYW${`{K!clSD5rvCEm(+^(r z=C->o9qhTEo$kJH`OBNv?0Mvs%U`VT{^Xi}J!GveufE{Ev)}z@ep~i;pJN{8cg^g- z@{jNA{o3!(WWFAI`;ot`I)3J%m+bU&=Uq$vin(*+&fw0&t$~vkg1&FzfBP=(hoZ6i z;`7IE+xpt`f0o|6^vdJDbK!-9eW(62Tzc{4zaA<_*JTUcXSU})zx{;E+{dO$6gQlE{v9{7oj`uD`)c#$+h4qEWBHPa zt(RT(EkncqIMmt4;d@_tBE5h2-|u;0-;-bAwr>9Z4fi};4s8m)N1SooOsg7r<{7)>1S*?^Q^Pi1fJg*2wdP_5OKVC4ufQsPx1^<(GaCD&m%`=7|3RA z;PMdIyq0rK+XSV^tXA(8S&KHpV=CgS!C=C68C&PmEra(Q8j<}jWvR%)4B|jYBY;~{ z3NhL!xVA=@bv_AMWdJ3sk#LNn=$R4TmT1)H9H2v6%tO$^6w>P5GMDf0NbFfn*s~f* zx>QJXu`IdB*4exyrC2bl>f~Tufvm|T2fB(f7}SO@r5NBc{;7-yg!xl-71Z-8hS4Ic z@-${`H>;uuRwXx^ZxIalM=^?kuEnA%X27Bw#zEX*jiNv-Gp6-$k~UOS!q8r@z;@-u z2c&RfU~!3oUX-WJaI~LffiiEEg_)38F`IIQxZ2T+LIq15V!F;!#7CY50xOO91uK}z z=doK*feLM_U8yV{cL$+VTiPVx6*@eq{4rrZHa+nTFUim4s(R>>j}fWv<>R z6~qep@0ry^SY#5e9-eiqh8xtwC_z~%!QbdvBm+Vt-_^s3TB#6g<_dD%azNS7LQ0CF zq%CPP!E7*YaI}%r!ef(hI~6IaRH!y12%6@ye$~lz4oImW1QH8KH(=oUxh^9Y!JsQK z7y#F{QyR2uBpX$343QwR5<&z_lBR;L3MQf5IRA8 z-){gtP5GTw=~YA^(GWN!@_Et&K+k*#w-EzK;Vj2_hC28HDQt;9$OF0YAh*dlXYy=O2rw9&woLce#zqqgj7goU2)Cvok6>z*1rxr{V zmo6;e*%k2IBEHlGX>nnR3o^k)4C6~lS8V0U98c>XY4M^Z5jv2v8 z7D%<&jBWJE0W8QZDWoLhM;o*_h9QUHXaWL~V%i26X$;T6A~mR1ra&FXnXojW2wlCc zr)HX-GF?(ip;E?}%(R4{m#jfhMPRm4Dyg^<3>D1^8`Q}pQwLOjHsZ{Px|10=k+3-E zwRnLk$F+;7A#EU$T}~y#m6d6RY51eW8DFHCJP_Gddv-b$3f8)cJgMhW9nC1S`FOo9 zE_IH=oIOGtm^iw?8c8u#V4z}#6EqpC5X&vns6*CBvoI-;aa%VDyHRFDaLI3ed^kwM zkQ6iFEapgnRMxXmIWc~AVMda zi&a=6n1{U&V>pHSyay`OJPTmV)jnjpB+(sP8<|}zxk34U9Els$BHw)A1~f3l9Q2!< zlo2WD10@AqlmavlU?$NX6Y+GI&)U>%xgIiW!EV8bjj5c}AS6 zF6$MWc|uuXqrx)PsR7>=+@2V5ML3jy zo>21IOhV$kS`)Tim(u2E^AtoKS5aZAg$lM5HQ)%f5UDZ3(?$HTXlA}p{9;N9Ju@P+ zk`k*KMcz&MX_x6NggQ!+iltUA+31iV*m;KV)e(fwJo%?&()v?z#IR&S`m{0}DJwOw z#0a)(&gR?Mfe~s%oOTry@wlXy#%E%kfNxd_xzuz)Sj`nW zj8{VG>I4V>V%4n94V6dbAtiof@m_85;Mq(MG#7(49Ig)z!S-Pv*iM z0j6VKJ)MPA*JEIp1ye&UrC`LdpkPC(YDiw8Fiq3dsVqAy`2>zbQG6 z6b}j%Mw7N&f^4OhY?V{8Yk93+CM?#iraWyl>JE|3b!!l#t!CaMUj;SGSAGOHNFNh1 zM^oh6!dG^)8G3$-`URvl61_4R{+Y&drMI+7snKz=THhBO5 literal 0 HcmV?d00001 diff --git a/data/quote_tdx_00806f050000.dat b/data/quote_tdx.bin similarity index 100% rename from data/quote_tdx_00806f050000.dat rename to data/quote_tdx.bin diff --git a/data/sgx_x509_extension.der b/data/sgx_x509_extension.der new file mode 100644 index 0000000000000000000000000000000000000000..97156cc022abcfc71ee3d4775f26678bca233c3b GIT binary patch literal 556 zcmXqLV$v{>W8>0l^Jx3w%gD>f$Rbd>>-m4i>f;(}Rl?JvXC{6(Xktu7Qo#h|hZ+d5 zal`d6F)}f-po%bIh%hrTGNY;7$RI4 zBHS1vJQyOp7$STaBK#O40vIBKOpM$H^2mM_Vi90rVP^jSp8*blorfeE>l4FDhrDA3E=U^K4a`tNkDZZ` R@jt2v6C+v(voizL004pcbLjv8 literal 0 HcmV?d00001 diff --git a/data/signing_cert.pem b/data/signing_cert.pem index d7763ab..303b44b 100644 --- a/data/signing_cert.pem +++ b/data/signing_cert.pem @@ -14,3 +14,19 @@ QEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG SM49BAMCA0cAMEQCIB9C8wOAN/ImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj ftbrNGsGU8YH211dRiYNoPPu19Zp/ze8JmhujB0oBw== -----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg +AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= +-----END CERTIFICATE----- diff --git a/data/tcb_info_v2.json b/data/tcb_info_v2.json new file mode 100644 index 0000000..663a379 --- /dev/null +++ b/data/tcb_info_v2.json @@ -0,0 +1,221 @@ +{ + "tcbInfo": { + "version": 2, + "issueDate": "2024-09-10T06:26:58Z", + "nextUpdate": "2024-10-10T06:26:58Z", + "fmspc": "00606a000000", + "pceId": "0000", + "tcbType": 0, + "tcbEvaluationDataNumber": 16, + "tcbLevels": [ + { + "tcb": { + "sgxtcbcomp01svn": 12, + "sgxtcbcomp02svn": 12, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "SWHardeningNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 12, + "sgxtcbcomp02svn": 12, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "ConfigurationAndSWHardeningNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 11, + "sgxtcbcomp02svn": 11, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-02-15T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 11, + "sgxtcbcomp02svn": 11, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-02-15T00:00:00Z", + "tcbStatus": "OutOfDateConfigurationNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 7, + "sgxtcbcomp02svn": 9, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2022-08-10T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 7, + "sgxtcbcomp02svn": 9, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2022-08-10T00:00:00Z", + "tcbStatus": "OutOfDateConfigurationNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 11 + }, + "tcbDate": "2021-11-10T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 10 + }, + "tcbDate": "2020-11-11T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 5 + }, + "tcbDate": "2018-01-04T00:00:00Z", + "tcbStatus": "OutOfDate" + } + ] + }, + "signature": "66902eb508294eea2e1c46b04b66c3c6db90498f8116ac3b3630562e917f7dc513800203f2e7572bdade7744b64b30c72006c96b500242159331329e0999f031" +} diff --git a/data/tcb_info_v3_with_tdx_module.json b/data/tcb_info_v3_with_tdx_module.json new file mode 100644 index 0000000..7f75a62 --- /dev/null +++ b/data/tcb_info_v3_with_tdx_module.json @@ -0,0 +1 @@ +{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2024-09-10T06:45:50Z","nextUpdate":"2024-10-10T06:45:50Z","fmspc":"00806f050000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":16,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"UpToDate"}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":6,"category":"BIOS","type":"Early Microcode Update"},{"svn":6,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":6,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00837"]},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00837"]}]},"signature":"8ebb019603151b45d4124598446c7d737f385e836b9b1a0ee5c0d5228378dcc3e6eb082796d8dcafb078d7fb2f19276ddfc4318c96083999b5e7ce5854ce9142"} diff --git a/data/tcbinfov3_00806f050000.json b/data/tcbinfov3_00806f050000.json deleted file mode 100644 index 3d9a480..0000000 --- a/data/tcbinfov3_00806f050000.json +++ /dev/null @@ -1 +0,0 @@ -{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2025-02-13T03:50:41Z","nextUpdate":"2025-03-15T03:50:41Z","fmspc":"00806f050000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":17,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":3},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate"}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":7,"category":"BIOS","type":"Early Microcode Update"},{"svn":7,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":7,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":6,"category":"BIOS","type":"Early Microcode Update"},{"svn":6,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":6,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00960","INTEL-SA-00982","INTEL-SA-00986"]},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00837","INTEL-SA-00960","INTEL-SA-00982","INTEL-SA-00986"]},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00837","INTEL-SA-00960","INTEL-SA-00982","INTEL-SA-00986"]}]},"signature":"efd79a5c3445d3af81068fc0bc6ac013a87c6aea91088c4373eac8de0b9a99ee27de6eaf3e28a6ad77f1199fb283edbb99ed8940b4dac568d78102e590a7dc71"} \ No newline at end of file diff --git a/help.sh b/help.sh new file mode 100755 index 0000000..fea0260 --- /dev/null +++ b/help.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# This script displays help information for the Makefile. +# Usage: ./help.sh Makefile + +# Set colors for output +col_off='\033[0m' +target_col='\033[36m' +variable_col='\033[93m' +grey='\033[90m' + +# Main function to display help information +help() { + # Display usage information + echo "Usage:" + printf " make %b[target]%b %b[variables]%b\n\n" "$target_col" "$col_off" "$variable_col" "$col_off" + + # Display targets information + _help_targets "$1" + + # Display variables information + _help_variables "$1" + + # Display examples + _help_examples +} + +# Function to display targets information +_help_targets() { + local pattern + pattern='^[a-zA-Z0-9._-]+:.*?##.*$' + + echo "Target(s):" + grep -E "$pattern" "$1" | sort | while read -r line; do + target=${line%%:*} + description=${line#*## } + printf " %b%-30s%b%s\n" "$target_col" "$target" "$col_off" "$description" + done + echo "" +} + +# Function to display variables information +_help_variables() { + local pattern + pattern='^[a-zA-Z0-9_-]+ [:?!+]?=.*?##.*$' + + echo "Variable(s):" + grep -E "$pattern" "$1" | sort | while read -r line; do + variable=${line%% *} + default=${line#*= } + default=${default%%##*} + description=${line##*## } + printf " %b%-30s%b%s %b(default: %s)%b\n" "$variable_col" "$variable" "$col_off" "$description" "$grey" "$default" "$col_off" + done + echo "" +} + +# Function to display examples +_help_examples() { + echo "Example(s):" + echo " make lint" + echo " make fmt" + echo " make build" +} + +# Call main function +help "$1" + +# Return exit code indicating success +exit 0 \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..45b8d22 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,12 @@ +edition = "2021" +version = "Two" + +imports_layout = "HorizontalVertical" +imports_granularity = "Module" +group_imports = "StdExternalCrate" +format_code_in_doc_comments = true + +comment_width = 100 +wrap_comments = true +error_on_line_overflow = true +match_block_trailing_comma = true diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index ab17f8c..0000000 --- a/src/constants.rs +++ /dev/null @@ -1,15 +0,0 @@ -// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteConstants.h - -pub const SGX_TEE_TYPE: u32 = 0x00000000; -pub const TDX_TEE_TYPE: u32 = 0x00000081; - -pub const ECDSA_256_WITH_P256_CURVE: u16 = 2; -pub const ECDSA_384_WITH_P384_CURVE: u16 = 3; - -pub const HEADER_LEN: usize = 48; - -pub const ENCLAVE_REPORT_LEN: usize = 384; -pub const TD10_REPORT_LEN: usize = 584; -pub const TD15_REPORT_LEN: usize = 684; - -pub const INTEL_QE_VENDOR_ID: [u8; 16] = [0x93, 0x9A, 0x72, 0x33, 0xF7, 0x9C, 0x4C, 0xA9, 0x94, 0x0A, 0x0D, 0xB3, 0x95, 0x7F, 0x06, 0x07]; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4e04b20..0325d69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,153 +1,455 @@ +pub mod trust_store; pub mod types; pub mod utils; -pub mod constants; -use x509_parser::certificate::X509Certificate; - -#[cfg(test)] -mod tests { - use crate::types::tcbinfo::{TcbInfoV2, TcbInfoV3}; - use crate::types::enclave_identity::EnclaveIdentityV2; - use crate::types::quotes::{version_4::QuoteV4, version_3::QuoteV3}; - use crate::types::collaterals::IntelCollateral; - - use crate::utils::cert::{hash_crl_keccak256, hash_x509_keccak256, parse_crl_der, parse_pem, parse_x509_der, verify_crl}; - use crate::utils::tcbinfo::{validate_tcbinfov2, validate_tcbinfov3, get_tcbinfov3_content_hash}; - use crate::utils::enclave_identity::get_enclave_identityv2_content_hash; - use crate::utils::quotes::{ - version_3::verify_quote_dcapv3, - version_4::verify_quote_dcapv4 +use std::time::SystemTime; + +use anyhow::{Context, anyhow, bail}; +use chrono::{DateTime, Utc}; +use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; +use trust_store::{TrustStore, TrustedIdentity}; +use types::{ + VerifiedOutput, + collateral::Collateral, + enclave_identity::QeTcbStatus, + quote::{AttestationKeyType, Quote, TDX_TEE_TYPE}, + sgx_x509::SgxPckExtension, + tcb_info::{TcbInfo, TcbStatus}, +}; +use utils::Expireable; +use x509_cert::der::{Any, DecodePem}; +use x509_verify::VerifyingKey as X509VerifyingKey; +use zerocopy::AsBytes; + +pub const INTEL_ROOT_CA_PEM: &str = "\ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi71OiO +SLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlA== +-----END PUBLIC KEY-----"; + +pub fn verify_dcap_quote( + current_time: SystemTime, + collateral: Collateral, + quote: Quote, +) -> anyhow::Result { + // 1. Verify the integrity of the signature chain from the Quote to the Intel-issued PCK + // certificate, and that no keys in the chain have been revoked. + let tcb_info = verify_integrity(current_time, &collateral, "e)?; + + // 2. Verify the Quoting Enclave source and all signatures in the Quote. + let qe_tcb_status = verify_quote(current_time, &collateral, "e)?; + + // 3. Verify the status of Intel SGX TCB described in the chain. + let pck_extension = quote.signature.get_pck_extension()?; + let (sgx_tcb_status, tdx_tcb_status, advisory_ids) = verify_tcb_status(&tcb_info, &pck_extension, "e)?; + + assert!( + sgx_tcb_status != TcbStatus::Revoked || tdx_tcb_status != TcbStatus::Revoked, + "FMPSC TCB Revoked" + ); + + let advisory_ids = if advisory_ids.is_empty() { + None + } else { + Some(advisory_ids) }; - // Pinned September 10th, 2024, 6:49am GMT - // there's no need for constant sample collateral updates - const PINNED_TIME: u64 = 1739419232; - - #[test] - fn test_root_crl_verify() { - let intel_sgx_root_ca = parse_x509_der(include_bytes!("../data/Intel_SGX_Provisioning_Certification_RootCA.cer")); - let intel_sgx_root_ca_crl = parse_crl_der(include_bytes!("../data/intel_root_ca_crl.der")); + // 4. If TDX type then verify the status of TDX Module status and converge and send + let mut tcb_status; + if quote.header.tee_type == TDX_TEE_TYPE { + tcb_status = tdx_tcb_status; + let tdx_module_status = + tcb_info.verify_tdx_module(quote.body.as_tdx_report_body().unwrap())?; + tcb_status = TcbInfo::converge_tcb_status_with_tdx_module(tcb_status, tdx_module_status); + } else { + tcb_status = sgx_tcb_status; + } - assert!(verify_crl(&intel_sgx_root_ca_crl, &intel_sgx_root_ca, PINNED_TIME)); + // 5. Converge platform TCB status with QE TCB status + tcb_status = TcbInfo::converge_tcb_status_with_qe_tcb(tcb_status, qe_tcb_status.into()); + + Ok(VerifiedOutput { + quote_version: quote.header.version.get(), + tee_type: quote.header.tee_type, + tcb_status, + fmspc: pck_extension.fmspc, + quote_body: quote.body, + advisory_ids, + }) +} + + + +fn verify_integrity( + current_time: SystemTime, + collateral: &Collateral, + quote: &Quote, +) -> anyhow::Result { + if !collateral + .tcb_info_and_qe_identity_issuer_chain + .valid_at(current_time) + { + bail!("expired tcb info issuer chain"); } + let pck_cert_chain_data = quote.signature.get_pck_cert_chain()?; - #[test] - fn test_tcbinfov3() { - // let current_time = chrono::Utc::now().timestamp() as u64; + if !pck_cert_chain_data.pck_cert_chain.valid_at(current_time) { + bail!("expired pck cert chain"); + } - let tcbinfov3_json = include_str!("../data/tcbinfov3_00806f050000.json"); - let tcbinfov3: TcbInfoV3 = serde_json::from_str(tcbinfov3_json).unwrap(); - let tcbinfov3_serialize = serde_json::to_string(&tcbinfov3).unwrap(); - assert!(tcbinfov3_serialize == tcbinfov3_json); + let root_ca = collateral + .tcb_info_and_qe_identity_issuer_chain + .last() + .context("tcb issuer chain is empty")?; - let sgx_signing_cert_pem = &parse_pem(include_bytes!("../data/signing_cert.pem")).unwrap()[0]; - let sgx_signing_cert = parse_x509_der(&sgx_signing_cert_pem.contents); + // Verify the root certificate is self issued + if root_ca.tbs_certificate.issuer != root_ca.tbs_certificate.subject { + bail!("root certificate is not self issued"); + } - assert!(validate_tcbinfov3(&tcbinfov3, &sgx_signing_cert, PINNED_TIME)); + let spki = x509_cert::spki::SubjectPublicKeyInfo::::from_pem(INTEL_ROOT_CA_PEM)?; + let intel_root_ca = X509VerifyingKey::try_from(spki).unwrap(); + intel_root_ca + .verify(root_ca) + .context("Root CA signature verification failed")?; + + // Build initial trust store with the root certificate + let mut trust_store = TrustStore::new(current_time, vec![root_ca.clone()])?; + + // Verify that the CRL is signed by Intel and add it to the store. + trust_store + .add_crl(collateral.root_ca_crl.clone(), true, None) + .context("failed to verify root ca crl")?; + + // Build intermediaries from the PCK cert chain, EXCLUDING the leaf certificate + let mut intermediaries = std::collections::BTreeMap::new(); + // Skip the first certificate (leaf) and only use intermediates and root + for cert in pck_cert_chain_data.pck_cert_chain.iter().skip(1) { + let subject = cert.tbs_certificate.subject.to_string(); + let pk = cert + .try_into() + .map_err(|e| anyhow::anyhow!("failed to decode key from certificate: {}", e))?; + + intermediaries.insert( + subject, + TrustedIdentity { + cert: cert.clone(), //TODO: remove clone eventually, or else may hit solana limits + pk, + }, + ); } - #[test] - fn test_tcbinfov2() { - // let current_time = chrono::Utc::now().timestamp() as u64; + trust_store + .add_crl(collateral.pck_crl.clone(), true, Some(&intermediaries)) + .context("failed to verify pck crl")?; + + // Verify PCK Cert Chain and add it to the store. + let pck_cert_chain_data = quote.signature.get_pck_cert_chain()?; + trust_store + .verify_chain_leaf(&pck_cert_chain_data.pck_cert_chain) + .context("failed to verify pck crl issuer chain")?; + + // Verify TCB Info Issuer Chain + let tcb_issuer = trust_store + .verify_chain_leaf(&collateral.tcb_info_and_qe_identity_issuer_chain) + .context("failed to verify tcb info issuer chain")?; + + // Get TCB Signer Public Key + let tcb_signer = tcb_issuer + .cert + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .context("missing tcb signer public key")?; + + // We are making big assumption here that the key is ECDSA P-256 + let tcb_signer = p256::ecdsa::VerifyingKey::from_sec1_bytes(tcb_signer) + .context("invalid tcb signer public key")?; + + // Verify the TCB Info + let tcb_info = collateral + .tcb_info + .as_tcb_info_and_verify(current_time, tcb_signer) + .context("failed to verify tcb info signature")?; + + // Verify the quote identity issuer chain + let _qe_id_issuer = trust_store + .verify_chain_leaf(&collateral.tcb_info_and_qe_identity_issuer_chain) + .context("failed to verify pck crl issuer certificate chain")?; + + Ok(tcb_info) +} + +fn verify_quote( + current_time: SystemTime, + collateral: &Collateral, + quote: &Quote, +) -> anyhow::Result { + let qe_tcb_status = verify_quote_enclave_source(current_time, collateral, quote)?; + verify_quote_signatures(quote)?; + Ok(qe_tcb_status) +} + +/// Verify the quote enclave source and return the TCB status +/// of the quoting enclave. +pub fn verify_quote_enclave_source( + current_time: SystemTime, + collateral: &Collateral, + quote: &Quote, +) -> anyhow::Result { + + // Verify that the enclave identity root is signed by root certificate + let qe_identity = collateral + .qe_identity + .validate_as_enclave_identity( + &VerifyingKey::from_sec1_bytes( + collateral.tcb_info_and_qe_identity_issuer_chain[0] + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .context("missing qe identity public key")?, + ) + .context("failed to verify quote enclave identity")?, + ) + .context("failed to verify quote enclave identity")?; + + // Validate that current time is between issue_date and next_update + let current_time: DateTime = current_time.into(); + if current_time < qe_identity.issue_date || current_time > qe_identity.next_update { + bail!("tcb info is not valid at current time"); + } - let tcbinfov2_json = include_str!("../data/tcbinfov2.json"); - let tcbinfov2: TcbInfoV2 = serde_json::from_str(tcbinfov2_json).unwrap(); - let tcbinfov2_serialize = serde_json::to_string(&tcbinfov2).unwrap(); - assert!(tcbinfov2_serialize == tcbinfov2_json); + // Compare the mr_signer values + if qe_identity.mrsigner != quote.signature.qe_report_body.mr_signer { + bail!( + "invalid qe mrsigner, expected {} but got {}", + hex::encode(qe_identity.mrsigner), + hex::encode(quote.signature.qe_report_body.mr_signer) + ); + } - let sgx_signing_cert_pem = &parse_pem(include_bytes!("../data/signing_cert.pem")).unwrap()[0]; - let sgx_signing_cert = parse_x509_der(&sgx_signing_cert_pem.contents); + // Compare the isv_prod_id values + if qe_identity.isvprodid != quote.signature.qe_report_body.isv_prod_id.get() { + bail!( + "invalid qe isv_prod_id, expected {} but got {}", + qe_identity.isvprodid, + quote.signature.qe_report_body.isv_prod_id.get() + ); + } - assert!(validate_tcbinfov2(&tcbinfov2, &sgx_signing_cert, PINNED_TIME)); + // Compare the attribute values + let qe_report_attributes = quote.signature.qe_report_body.sgx_attributes; + let calculated_mask = qe_identity + .attributes_mask + .iter() + .zip(qe_report_attributes.iter()) + .map(|(&mask, &attribute)| mask & attribute); + + if calculated_mask + .zip(qe_identity.attributes) + .any(|(masked, identity)| masked != identity) + { + bail!("qe attrtibutes mismatch"); } - #[test] - fn test_quotev4() { - let quotev4_slice = include_bytes!("../data/quote_tdx_00806f050000.dat"); - let quotev4 = QuoteV4::from_bytes(quotev4_slice); - assert_eq!(quotev4.header.version, 4); + // Compare misc_select values + let misc_select = quote.signature.qe_report_body.misc_select; + let calculated_mask = qe_identity + .miscselect_mask + .as_bytes() + .iter() + .zip(misc_select.as_bytes().iter()) + .map(|(&mask, &attribute)| mask & attribute); + + if calculated_mask + .zip(qe_identity.miscselect.as_bytes().iter()) + .any(|(masked, &identity)| masked != identity) + { + bail!("qe misc_select mismatch"); } - #[test] - fn test_verifyv3() { - // let current_time = chrono::Utc::now().timestamp() as u64; + let qe_tcb_status = qe_identity.get_qe_tcb_status(quote.signature.qe_report_body.isv_svn.get()); - let mut collaterals = IntelCollateral::new(); - collaterals.set_tcbinfo_bytes(include_bytes!("../data/tcbinfov2.json")); - collaterals.set_qeidentity_bytes(include_bytes!("../data/qeidentityv2.json")); - collaterals.set_intel_root_ca_der(include_bytes!("../data/Intel_SGX_Provisioning_Certification_RootCA.cer")); - collaterals.set_sgx_tcb_signing_pem(include_bytes!("../data/signing_cert.pem")); - collaterals.set_sgx_intel_root_ca_crl_der(include_bytes!("../data/intel_root_ca_crl.der")); - collaterals.set_sgx_platform_crl_der(include_bytes!("../data/pck_platform_crl.der")); - // collaterals.set_sgx_processor_crl_der(include_bytes!("../data/pck_processor_crl.der")); + Ok(qe_tcb_status) +} +/// Verify the quote signatures. +pub fn verify_quote_signatures(quote: &Quote) -> anyhow::Result<()> { + let pck_cert_chain_data = quote.signature.get_pck_cert_chain()?; + let pck_pk_bytes = pck_cert_chain_data.pck_cert_chain[0] + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .context("missing pck public key")?; - let dcap_quote_bytes = hex::decode("030002000000000009000e00939a7233f79c4ca9940a0db3957f0607ad04024c9dfb382baf51ca3e5d6cb6e6000000000c0c100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000e700000000000000a4f45c39dac622cb1dd32ddb35a52ec92db41d0fa88a1c911c49e59c534f61cd00000000000000000000000000000000000000000000000000000000000000001bda23eb3a807dfe735ddcebbfa2eac05e04a00df2804296612f770b594180ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca100000e78d2532cbef391dea9a477119bc505b47e187f6f045636cce8bcf41604a099232eee31b3ef3827c442eb5d5981610480deb0625ed4b01c1ac2b0fb43e05efdeab8af342a611fb608193d9a47b8111654172adf2dabd2d428d28ebe094b9baa1f8f7e240b015af174d4f58a6b201946eee2097af02ed554909779ea2d9f3c1020c0c100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000001500000000000000e700000000000000192aa50ce1c0cef03ccf89e7b5b16b0d7978f5c2b1edcf774d87702e8154d8bf00000000000000000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a82754acc7010b3c087c6425ccf47033f711fa44776c6df3cf744864a063657b00000000000000000000000000000000000000000000000000000000000000006cf7ecfde138b32bbf6aec5e260f8bb6277cc2876ea144c3995d2afc0e6baa3525d91884672bf2832c23a6ebf85a165b45af53c836a31168ff7deaec0dd9c82c2000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0500620e00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494945386a4343424a6d674177494241674956414b7750766270377a6f7a50754144646b792b6f526e356f36704d754d416f4743437147534d343942414d430a4d484178496a416742674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d0a45556c756447567349454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155450a4341774351304578437a414a42674e5642415954416c56544d4234584454497a4d4467794e4449784d7a557a4d6c6f5844544d774d4467794e4449784d7a557a0a4d6c6f77634445694d434147413155454177775a535735305a5777675530645949464244537942445a584a3061575a70593246305a5445614d426747413155450a43677752535735305a577767513239796347397959585270623234784644415342674e564241634d43314e68626e526849454e7359584a684d517377435159440a5651514944414a445154454c4d416b474131554542684d4356564d775754415442676371686b6a4f5051494242676771686b6a4f50514d4242774e43414154450a764b6a754b66376969723832686d2b4d5a4151452b6847643349716d53396235634e63484a754b7a5a445970626f35496a344c7a7176704f503830706f4152730a59504233594e355537704d3777644936314b66716f344944446a434341776f77487759445652306a42426777466f41556c5739647a62306234656c4153636e550a3944504f4156634c336c5177617759445652306642475177596a42676f46366758495a616148523063484d364c79396863476b7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253397a5a3367765932567964476c6d61574e6864476c76626939324d7939775932746a636d772f593245390a6347786864475a76636d306d5a57356a62325270626d63395a4756794d4230474131556444675157424251695a7667373930317a3171554d3874534c754358580a6571314c6f54414f42674e56485138424166384542414d434273417744415944565230544151482f4241497741444343416a734743537147534962345451454e0a41515343416977776767496f4d42344743697147534962345451454e41514545454358343464705036434c5154772f785543575448306b776767466c42676f710a686b69472b453042445145434d4949425654415142677371686b69472b45304244514543415149424444415142677371686b69472b45304244514543416749420a4444415142677371686b69472b4530424451454341774942417a415142677371686b69472b4530424451454342414942417a415242677371686b69472b4530420a4451454342514943415038774551594c4b6f5a496876684e41513042416759434167442f4d42414743797147534962345451454e41514948416745424d4241470a43797147534962345451454e41514949416745414d42414743797147534962345451454e4151494a416745414d42414743797147534962345451454e4151494b0a416745414d42414743797147534962345451454e4151494c416745414d42414743797147534962345451454e4151494d416745414d42414743797147534962340a5451454e4151494e416745414d42414743797147534962345451454e4151494f416745414d42414743797147534962345451454e41514950416745414d4241470a43797147534962345451454e41514951416745414d42414743797147534962345451454e415149524167454e4d42384743797147534962345451454e415149530a4242414d44414d442f2f38424141414141414141414141414d42414743697147534962345451454e41514d45416741414d42514743697147534962345451454e0a4151514542674267616741414144415042676f71686b69472b45304244514546436745424d42344743697147534962345451454e4151594545424531784169510a72743945363234433159516b497034775241594b4b6f5a496876684e41513042427a41324d42414743797147534962345451454e415163424151482f4d4241470a43797147534962345451454e41516343415145414d42414743797147534962345451454e41516344415145414d416f4743437147534d343942414d43413063410a4d45514349445a6f63514c6478362b4f2b586d4f6b766f6b654133345a617261342b6539534e5877344b68396d5876574169415479695a6e495932474f3466670a4938673342666c4e434f56446e42505270507559377274484e77335470513d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436c6a4343416a32674177494241674956414a567658633239472b487051456e4a3150517a7a674658433935554d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484178496a41670a42674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d45556c75644756730a49454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b474131554543417743513045780a437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741454e53422f377432316c58534f0a3243757a7078773734654a423732457944476757357258437478327456544c7136684b6b367a2b5569525a436e71523770734f766771466553786c6d546c4a6c0a65546d693257597a33714f42757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f536347724442530a42674e5648523845537a424a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e5648513445466751556c5739640a7a62306234656c4153636e553944504f4156634c336c517744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159420a4166384341514177436759494b6f5a497a6a30454177494452774177524149675873566b6930772b6936565947573355462f32327561586530594a446a3155650a6e412b546a44316169356343494359623153416d4435786b66545670766f34556f79695359787244574c6d5552344349394e4b7966504e2b0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a00").unwrap(); - let dcap_quote = QuoteV3::from_bytes(&dcap_quote_bytes); + let pck_pkey = VerifyingKey::from_sec1_bytes(pck_pk_bytes) + .map_err(|e| anyhow!("failed to parse pck public key: {}", e))?; - let verified_output = verify_quote_dcapv3(&dcap_quote, &collaterals, PINNED_TIME); + let qe_report_signature = Signature::from_slice(quote.signature.qe_report_signature)?; + pck_pkey + .verify( + quote.signature.qe_report_body.as_bytes(), + &qe_report_signature, + ) + .map_err(|e| anyhow!("failed to verify qe report signature. {e}"))?; - println!("{:?}", verified_output); - let root_hash = hash_x509_keccak256(&collaterals.get_sgx_intel_root_ca()); - let sign_hash = hash_x509_keccak256(&collaterals.get_sgx_tcb_signing()); - let crl_hash = hash_crl_keccak256(&collaterals.get_sgx_intel_root_ca_crl().unwrap()); - println!("{:?}", root_hash); - println!("{:?}", sign_hash); - println!("{:?}", crl_hash); + quote.signature.verify_qe_report()?; + + let mut key = [0u8; 65]; + key[0] = 4; + key[1..].copy_from_slice(quote.signature.attestation_pub_key); + + if quote.header.attestation_key_type.get() != AttestationKeyType::Ecdsa256P256 as u16 { + bail!("unsupported attestation key type"); } - #[test] - fn test_verifyv4() { - // let current_time = chrono::Utc::now().timestamp() as u64; + let attest_key = VerifyingKey::from_sec1_bytes(&key) + .map_err(|e| anyhow!("failed to parse attest key: {e}"))?; + + let header_bytes = quote.header.as_bytes(); + let body_bytes = quote.body.as_bytes(); + let mut data = Vec::with_capacity(header_bytes.len() + body_bytes.len()); + data.extend_from_slice(header_bytes); + data.extend_from_slice(body_bytes); + + let sig = Signature::from_slice(quote.signature.isv_signature)?; + attest_key + .verify(&data, &sig) + .context("failed to verify quote signature")?; + + Ok(()) +} + +/// Ensure the latest tcb info is not revoked, and is either up to date or only needs a configuration +/// change. +pub fn verify_tcb_status( + tcb_info: &TcbInfo, + pck_extension: &SgxPckExtension, + quote: &Quote, +) -> anyhow::Result<(TcbStatus, TcbStatus, Vec)> { + // Make sure the tcb_info matches the enclave's model/PCE version + if pck_extension.fmspc != tcb_info.fmspc { + return Err(anyhow::anyhow!( + "tcb fmspc mismatch (pck extension: {:?}, tcb_info: {:?})", + pck_extension.fmspc, + tcb_info.fmspc + )); + } - let mut collaterals = IntelCollateral::new(); - collaterals.set_tcbinfo_bytes(include_bytes!("../data/tcbinfov3_00806f050000.json")); - collaterals.set_qeidentity_bytes(include_bytes!("../data/qeidentityv2_apiv4.json")); - collaterals.set_intel_root_ca_der(include_bytes!("../data/Intel_SGX_Provisioning_Certification_RootCA.cer")); - collaterals.set_sgx_tcb_signing_pem(include_bytes!("../data/signing_cert.pem")); - collaterals.set_sgx_intel_root_ca_crl_der(include_bytes!("../data/intel_root_ca_crl.der")); - collaterals.set_sgx_platform_crl_der(include_bytes!("../data/pck_platform_crl.der")); - // collaterals.set_sgx_processor_crl_der(include_bytes!("../data/pck_processor_crl.der")); + if pck_extension.pceid != tcb_info.pce_id { + return Err(anyhow::anyhow!( + "tcb pceid mismatch (pck extension: {:?}, tcb_info: {:?})", + pck_extension.pceid, + tcb_info.pce_id + )); + } + TcbStatus::lookup(pck_extension, tcb_info, quote) +} - let dcap_quote = QuoteV4::from_bytes(include_bytes!("../data/quote_tdx_00806f050000.dat")); +#[cfg(test)] +mod tests { + + use std::time::Duration; + + use x509_cert::{crl::CertificateList, der::Decode}; + + use crate::{ + types::{ + enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature, + }, + utils::cert_chain_processor, + }; - let verified_output = verify_quote_dcapv4(&dcap_quote, &collaterals, PINNED_TIME); + use super::*; - println!("{:?}", verified_output); - let root_hash = hash_x509_keccak256(&collaterals.get_sgx_intel_root_ca()); - let sign_hash = hash_x509_keccak256(&collaterals.get_sgx_tcb_signing()); - let crl_hash = hash_crl_keccak256(&collaterals.get_sgx_intel_root_ca_crl().unwrap()); - println!("{:?}", root_hash); - println!("{:?}", sign_hash); - println!("{:?}", crl_hash); + fn sgx_quote_data() -> (Collateral, Quote<'static>) { + let collateral = include_str!("../data/full_collateral_sgx.json"); + let collateral: Collateral = serde_json::from_str(collateral).unwrap(); + let quote = include_bytes!("../data/quote_sgx.bin"); + let quote = Quote::read(&mut quote.as_slice()).unwrap(); + (collateral, quote) } - #[test] - fn test_tcb_v3_tdx_content_hash() { - let tcb_v3_tdx_0_json_string = include_str!("../data/tcb_info_v3_tdx_0.json"); - let tcb_v3_tdx_1_json_string = include_str!("../data/tcb_info_v3_tdx_1.json"); + fn tdx_quote_data() -> (Collateral, Quote<'static>) { + let quote = include_bytes!("../data/quote_tdx.bin"); + let quote = Quote::read(&mut quote.as_slice()).unwrap(); + + let tcb_info_and_qe_identity_issuer_chain = include_bytes!("../data/signing_cert.pem"); + let tcb_info_and_qe_identity_issuer_chain = + cert_chain_processor::load_pem_chain_bpf_friendly( + tcb_info_and_qe_identity_issuer_chain, + ) + .unwrap(); + + let root_ca_crl = include_bytes!("../data/intel_root_ca_crl.der"); + let root_ca_crl = CertificateList::from_der(root_ca_crl).unwrap(); + + let tcb_info = include_bytes!("../data/tcb_info_v3_with_tdx_module.json"); + let tcb_info: TcbInfoAndSignature = serde_json::from_slice(tcb_info).unwrap(); + + let qe_identity = include_bytes!("../data/qeidentityv2_apiv4.json"); + let qe_identity: QuotingEnclaveIdentityAndSignature = + serde_json::from_slice(qe_identity).unwrap(); + + let platform_ca_crl = include_bytes!("../data/pck_platform_crl.der"); + let platform_ca_crl = CertificateList::from_der(platform_ca_crl).unwrap(); + + let collateral = Collateral { + tcb_info_and_qe_identity_issuer_chain, + root_ca_crl, + pck_crl: platform_ca_crl, + tcb_info, + qe_identity, + }; + (collateral, quote) + } - let tcb_v3_tdx_0: TcbInfoV3 = serde_json::from_str(&tcb_v3_tdx_0_json_string).unwrap(); - let tcb_v3_tdx_1: TcbInfoV3 = serde_json::from_str(&tcb_v3_tdx_1_json_string).unwrap(); + fn test_sgx_time() -> SystemTime { + // Aug 29th 4:20pm, ~24 hours after quote was generated + SystemTime::UNIX_EPOCH + Duration::from_secs(1724962800) + } - let tcb_v3_tdx_0_content_hash = get_tcbinfov3_content_hash(&tcb_v3_tdx_0); - let tcb_v3_tdx_1_content_hash = get_tcbinfov3_content_hash(&tcb_v3_tdx_1); - - assert_eq!(tcb_v3_tdx_0_content_hash, tcb_v3_tdx_1_content_hash); + fn test_tdx_time() -> SystemTime { + // Pinned September 10th, 2024, 6:49am GMT + SystemTime::UNIX_EPOCH + Duration::from_secs(1725950994) } #[test] - fn test_identity_v2_content_hash() { - let identity_v2_0_json_string = include_str!("../data/identityv2_0.json"); - let identity_v2_1_json_string = include_str!("../data/identityv2_1.json"); + fn parse_tdx_quote() { + let bytes = include_bytes!("../data/quote_tdx.bin"); + let quote = Quote::read(&mut bytes.as_slice()).unwrap(); + println!("{:?}", quote); + } - let identity_v2_0: EnclaveIdentityV2 = serde_json::from_str(&identity_v2_0_json_string).unwrap(); - let identity_v2_1: EnclaveIdentityV2 = serde_json::from_str(&identity_v2_1_json_string).unwrap(); - - let identity_v2_0_content_hash = get_enclave_identityv2_content_hash(&identity_v2_0); - let identity_v2_1_content_hash = get_enclave_identityv2_content_hash(&identity_v2_1); + #[test] + fn parse_sgx_quote() { + let bytes = include_bytes!("../data/quote_sgx.bin"); + let quote = Quote::read(&mut bytes.as_slice()).unwrap(); + println!("{:?}", quote); + } + + #[test] + fn verify_integrity() { + let (collateral, quote) = sgx_quote_data(); + super::verify_integrity(test_sgx_time(), &collateral, "e) + .expect("certificate chain integrity should succeed"); + } - assert_eq!(identity_v2_0_content_hash, identity_v2_1_content_hash); + #[test] + fn e2e_sgx_quote() { + let (collateral, quote) = sgx_quote_data(); + super::verify_dcap_quote(test_sgx_time(), collateral, quote) + .expect("certificate chain integrity should succeed"); + } + + #[test] + fn e2e_tdx_quote() { + let (collateral, quote) = tdx_quote_data(); + super::verify_dcap_quote(test_tdx_time(), collateral, quote) + .expect("certificate chain integrity should succeed"); } -} \ No newline at end of file +} diff --git a/src/trust_store.rs b/src/trust_store.rs new file mode 100644 index 0000000..2de4b99 --- /dev/null +++ b/src/trust_store.rs @@ -0,0 +1,189 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + time::SystemTime, +}; + +use anyhow::bail; +use x509_cert::{certificate::CertificateInner, crl::CertificateList}; +use x509_verify::VerifyingKey; + +use crate::utils::Expireable; + +/// TrustStore is a specialized PKI (Public Key Infrastructure) implementation designed for SGX +/// attestation verification. It manages certificate chains, validates signatures, aand enforces +/// revocation checking to establish secure chains of trust from Intel's root certificates to +/// attestation data. +pub struct TrustStore { + /// Trusted CAs (Certificate Authorities) + pub trusted: BTreeMap, + /// Trusted certificate revocation list. + pub crl: BTreeMap>, + /// Current time for validity checks + pub current_time: SystemTime, +} + +/// Wrapper for pre-parse trusted identity for verification. +#[derive(Debug, Clone)] +pub struct TrustedIdentity { + pub cert: CertificateInner, + pub pk: VerifyingKey, +} + +impl TrustStore { + /// Creates a new trust store with the given root certificates + /// + /// # Parameters + /// * `current_time` - Time reference for validity checks + /// * `roots` - Initial set of trusted root certificates + /// + /// # Security Considerations + /// * The provided roots establish the foundation of trust + /// * Current_time must come from a secure source on production systems + pub fn new( + current_time: SystemTime, + trusted_certs: Vec, + ) -> anyhow::Result { + let mut trusted = BTreeMap::new(); + + for cert in trusted_certs { + let pk = (&cert) + .try_into() + .map_err(|e| anyhow::anyhow!("failed to decode key from certificate: {}", e))?; + + trusted.insert( + cert.tbs_certificate.subject.to_string(), + TrustedIdentity { cert, pk }, + ); + } + + Ok(Self { + trusted, + crl: BTreeMap::new(), + current_time, + }) + } + + pub fn add_crl( + &mut self, + crl: CertificateList, + verify_signature: bool, + intermediaries: Option<&BTreeMap>, + ) -> anyhow::Result<()> { + // Verify signature if requested + if verify_signature { + let issuer = crl.tbs_cert_list.issuer.to_string(); + let signer = self.find_issuer(issuer, intermediaries)?; + + signer + .pk + .verify_strict(&crl) + .map_err(|e| anyhow::anyhow!("failed to verify crl signature: {}", e))?; + } + + // Store revoked certificates by issuer + let issuer = crl.tbs_cert_list.issuer.to_string(); + let issuer_revoked = self.crl.entry(issuer).or_default(); + + // Add all revoked certificates + if let Some(revoked_certs) = crl.tbs_cert_list.revoked_certificates { + for cert in revoked_certs { + issuer_revoked.insert(cert.serial_number.to_string()); + } + } + + Ok(()) + } + + /// Verify the leaf node in a certificate chain is rooted in the trust store + /// and does not use any revoked certificates. + /// + /// # Parameters + /// * `chain` - The certificate chain to verify. + /// + pub fn verify_chain_leaf( + &mut self, + chain: &[CertificateInner], + ) -> anyhow::Result { + // If the chain is empty, it is not valid + if chain.is_empty() { + bail!("certificate chain is empty"); + } + + // If the chain is expired, it is not valid + if !chain.valid_at(self.current_time) { + bail!("certificate chain is expired"); + } + + // Work through the certificate chain from the root (last) certificate. + let mut chain = chain.iter().rev().peekable(); + let mut intermediary = BTreeMap::new(); + + loop { + let cert = chain.next().expect("should have returned after leaf"); + let issuer = cert.tbs_certificate.issuer.to_string(); + let subject = cert.tbs_certificate.subject.to_string(); + + // Ensure the certificate is not revoked. + self.check_crls(cert)?; + let signer = self.find_issuer(issuer, Some(&intermediary))?; + + // Validate issuer signature. + signer + .pk + .verify_strict(cert) + .map_err(|e| anyhow::anyhow!("failed to verify issuer signature: {}", e))?; + + let pk = (cert) + .try_into() + .map_err(|e| anyhow::anyhow!("failed to decode key from certificate: {}", e))?; + + let identity = TrustedIdentity { + cert: cert.clone(), + pk, + }; + + if chain.peek().is_none() { + // If we are at the leaf node of the chain, discard intermediary identities. + // and return the verified identity. + self.trusted.extend(intermediary); + self.trusted.insert(subject, identity.clone()); + return Ok(identity); + } else { + // Otherwise, add the identity to the intermediary store. + intermediary.insert(subject, identity); + } + } + } + + /// Check the current crls to ensure a certificate is not revoked + fn check_crls(&self, cert: &CertificateInner) -> anyhow::Result<()> { + let issuer = cert.tbs_certificate.issuer.to_string(); + let serial = cert.tbs_certificate.serial_number.to_string(); + + // Check if this issuer has any revoked certificates + if let Some(issuer_revoked) = self.crl.get(&issuer) { + if issuer_revoked.contains(&serial) { + bail!("certificate is revoked"); + } + } + + Ok(()) + } + + /// Find an issuer in the trusted or intermediary stores + fn find_issuer<'a>( + &'a self, + issuer: String, + intermediary: Option<&'a BTreeMap>, + ) -> anyhow::Result<&'a TrustedIdentity> { + if let Some(signer) = self.trusted.get(&issuer) { + return Ok(signer); + } + if let Some(intermediary) = intermediary { + if let Some(signer) = intermediary.get(&issuer) { + return Ok(signer); + } + } + bail!("failed to find trusted issuer") + } +} diff --git a/src/types/cert.rs b/src/types/cert.rs deleted file mode 100644 index 2a6eeeb..0000000 --- a/src/types/cert.rs +++ /dev/null @@ -1,120 +0,0 @@ -use serde::{Serialize, Deserialize}; -use x509_parser::{certificate::X509Certificate, revocation_list::CertificateRevocationList}; - -use crate::utils::cert::{get_crl_uri, is_cert_revoked, parse_x509_der_multi, pem_to_der}; - -use super::collaterals::IntelCollateral; - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SgxExtensionTcbLevel { - pub sgxtcbcomp01svn: u8, - pub sgxtcbcomp02svn: u8, - pub sgxtcbcomp03svn: u8, - pub sgxtcbcomp04svn: u8, - pub sgxtcbcomp05svn: u8, - pub sgxtcbcomp06svn: u8, - pub sgxtcbcomp07svn: u8, - pub sgxtcbcomp08svn: u8, - pub sgxtcbcomp09svn: u8, - pub sgxtcbcomp10svn: u8, - pub sgxtcbcomp11svn: u8, - pub sgxtcbcomp12svn: u8, - pub sgxtcbcomp13svn: u8, - pub sgxtcbcomp14svn: u8, - pub sgxtcbcomp15svn: u8, - pub sgxtcbcomp16svn: u8, - pub pcesvn: u16, - pub cpusvn: [u8; 16] -} - - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SgxExtensions { - pub ppid: [u8; 16], - pub tcb: SgxExtensionTcbLevel, - pub pceid: [u8; 2], - pub fmspc: [u8; 6], - pub sgx_type: u32, - pub platform_instance_id: Option<[u8; 16]>, - pub configuration: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct PckPlatformConfiguration { - pub dynamic_platform: Option, - pub cached_keys: Option, - pub smt_enabled: Option, -} - -#[derive(Debug)] -pub struct IntelSgxCrls<'a> { - pub sgx_root_ca_crl: Option>, - pub sgx_pck_processor_crl: Option>, - pub sgx_pck_platform_crl: Option>, -} - -impl<'a> IntelSgxCrls<'a> { - pub fn new(sgx_root_ca_crl: Option>, sgx_pck_processor_crl: Option>, sgx_pck_platform_crl: Option>) -> Self { - Self { - sgx_root_ca_crl, - sgx_pck_processor_crl, - sgx_pck_platform_crl, - } - } - - pub fn from_collaterals(collaterals: &'a IntelCollateral) -> Self { - let sgx_root_ca_crl = collaterals.get_sgx_intel_root_ca_crl(); - let sgx_pck_processor_crl = collaterals.get_sgx_pck_processor_crl(); - let sgx_pck_platform_crl = collaterals.get_sgx_pck_platform_crl(); - - Self::new(sgx_root_ca_crl, sgx_pck_processor_crl, sgx_pck_platform_crl) - } - - pub fn is_cert_revoked(&self, cert: &X509Certificate) -> bool { - let crl = match get_crl_uri(cert) { - Some(crl_uri) => { - if crl_uri.contains("https://api.trustedservices.intel.com/sgx/certification/v3/pckcrl?ca=platform") - || crl_uri.contains("https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform") { - self.sgx_pck_platform_crl.as_ref() - } else if crl_uri.contains("https://api.trustedservices.intel.com/sgx/certification/v3/pckcrl?ca=processor") - || crl_uri.contains("https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=processor") { - self.sgx_pck_processor_crl.as_ref() - } else if crl_uri.contains("https://certificates.trustedservices.intel.com/IntelSGXRootCA.der") { - self.sgx_root_ca_crl.as_ref() - } else { - panic!("Unknown CRL URI: {}", crl_uri); - } - }, - None => { - panic!("No CRL URI found in certificate"); - } - }.unwrap(); - - // check if the cert is revoked given the crl - is_cert_revoked(cert, crl) - } -} - -#[derive(Debug, Clone)] -pub struct Certificates { - pub certs_der: Vec, -} - -impl Certificates { - pub fn from_der(certs_der: &[u8]) -> Self { - Self { - certs_der: certs_der.to_vec(), - } - } - - pub fn from_pem(pem_bytes: &[u8]) -> Self { - let certs_der = pem_to_der(pem_bytes); - Self::from_der(&certs_der) - } - - pub fn get_certs(&self) -> Vec { - let certs = parse_x509_der_multi(&self.certs_der); - certs - } -} \ No newline at end of file diff --git a/src/types/collateral.rs b/src/types/collateral.rs new file mode 100644 index 0000000..7c3be35 --- /dev/null +++ b/src/types/collateral.rs @@ -0,0 +1,50 @@ +use crate::utils::{cert_chain, crl}; +use serde::{Deserialize, Serialize}; +use x509_cert::{Certificate, crl::CertificateList}; + +use super::{enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Collateral { + /* Certificate Revocation List */ + /// Root CA CRL in PEM format + /// Contains a list of revoked certificates signed by Intel SGX Root CA. + /// It is used to check if any certificates in the verification chain have been revoked. + #[serde(with = "crl")] + pub root_ca_crl: CertificateList, + + /// PCK CRL in PEM format + /// + /// This can be Platform CA CRL or Processor CA CRL. + /// Contains a list of revoked certificates signed by Intel SGX Platform CA or Intel SGX Processor CA. + /// It is used to check if any certificates in the verification chain have been revoked. + /// Only to be passed if the quote is expected to be signed by Intel SGX PCK CA. + #[serde(with = "crl")] + pub pck_crl: CertificateList, + + /* Issuer Certificate Chains */ + /// TCB Info and Identity Issuer Chain in PEM format + /// Chain of certificates used to verify TCB Info and Identity signature. + #[serde(with = "cert_chain")] + pub tcb_info_and_qe_identity_issuer_chain: Vec, + + /* Structured Data */ + /// TCB Info Structure + /// Contains security version information and TCB levels. + pub tcb_info: TcbInfoAndSignature, + + /// QE Identity Structure + /// Contains Quoting Enclave identity information. + pub qe_identity: QuotingEnclaveIdentityAndSignature, +} + +#[cfg(test)] +mod tests { + use super::Collateral; + + #[test] + fn encode_decode_collateral_json() { + let json = include_str!("../../data/full_collateral_sgx.json"); + let _collateral: Collateral = serde_json::from_str(json).expect("json to parse"); + } +} diff --git a/src/types/collaterals.rs b/src/types/collaterals.rs deleted file mode 100644 index d051675..0000000 --- a/src/types/collaterals.rs +++ /dev/null @@ -1,349 +0,0 @@ -use x509_parser::{certificate::X509Certificate, revocation_list::CertificateRevocationList}; - -use super::enclave_identity::EnclaveIdentityV2; -use super::tcbinfo::{TcbInfoV2, TcbInfoV3}; - -use crate::utils::cert::{parse_crl_der, parse_x509_der, parse_x509_der_multi, pem_to_der}; - -#[derive(Clone, Debug)] -pub struct IntelCollateral { - pub tcbinfo_bytes: Option>, - pub qeidentity_bytes: Option>, - pub sgx_intel_root_ca_der: Option>, - pub sgx_tcb_signing_der: Option>, - pub sgx_pck_certchain_der: Option>, - pub sgx_intel_root_ca_crl_der: Option>, - pub sgx_pck_processor_crl_der: Option>, - pub sgx_pck_platform_crl_der: Option>, -} - -// builder pattern for IntelCollateralV3 -impl IntelCollateral { - pub fn new() -> IntelCollateral { - IntelCollateral { - tcbinfo_bytes: None, - qeidentity_bytes: None, - sgx_intel_root_ca_der: None, - sgx_tcb_signing_der: None, - sgx_pck_certchain_der: None, - sgx_intel_root_ca_crl_der: None, - sgx_pck_processor_crl_der: None, - sgx_pck_platform_crl_der: None, - } - } - - pub fn to_bytes(&self) -> Vec { - // serialization scheme is simple: the bytestream is made of 2 parts - // the first contains a u32 length for each of the members - // the second contains the actual data - // [lengths of each of the member][data segment] - - let tcbinfo_bytes = match self.tcbinfo_bytes { - Some(ref tcbinfo) => tcbinfo.as_slice(), - None => &[], - }; - - let qeidentity_bytes = match self.qeidentity_bytes { - Some(ref qeidentity) => qeidentity.as_slice(), - None => &[], - }; - - let sgx_intel_root_ca_der_bytes = match &self.sgx_intel_root_ca_der { - Some(der) => der.as_slice(), - None => &[], - }; - - let sgx_tcb_signing_der_bytes = match &self.sgx_tcb_signing_der { - Some(der) => der.as_slice(), - None => &[], - }; - - let sgx_pck_certchain_der_bytes = match &self.sgx_pck_certchain_der { - Some(der) => der.as_slice(), - None => &[], - }; - - let sgx_intel_root_ca_crl_der_bytes = match &self.sgx_intel_root_ca_crl_der { - Some(der) => der.as_slice(), - None => &[], - }; - - let sgx_pck_processor_crl_der_bytes = match &self.sgx_pck_processor_crl_der { - Some(der) => der.as_slice(), - None => &[], - }; - - let sgx_pck_platform_crl_der_bytes = match &self.sgx_pck_platform_crl_der { - Some(der) => der.as_slice(), - None => &[], - }; - - // get the total length - let total_length = 4 * 8 + tcbinfo_bytes.len() + qeidentity_bytes.len() + sgx_intel_root_ca_der_bytes.len() + sgx_tcb_signing_der_bytes.len() + sgx_pck_certchain_der_bytes.len() + sgx_intel_root_ca_crl_der_bytes.len() + sgx_pck_processor_crl_der_bytes.len() + sgx_pck_platform_crl_der_bytes.len(); - - // create the vec and copy the data - let mut data = Vec::with_capacity(total_length); - data.extend_from_slice(&(tcbinfo_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(qeidentity_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_intel_root_ca_der_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_tcb_signing_der_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_pck_certchain_der_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_intel_root_ca_crl_der_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_pck_processor_crl_der_bytes.len() as u32).to_le_bytes()); - data.extend_from_slice(&(sgx_pck_platform_crl_der_bytes.len() as u32).to_le_bytes()); - - data.extend_from_slice(&tcbinfo_bytes); - data.extend_from_slice(&qeidentity_bytes); - data.extend_from_slice(&sgx_intel_root_ca_der_bytes); - data.extend_from_slice(&sgx_tcb_signing_der_bytes); - data.extend_from_slice(&sgx_pck_certchain_der_bytes); - data.extend_from_slice(&sgx_intel_root_ca_crl_der_bytes); - data.extend_from_slice(&sgx_pck_processor_crl_der_bytes); - data.extend_from_slice(&sgx_pck_platform_crl_der_bytes); - - data - } - - pub fn from_bytes(slice: &[u8]) -> Self { - // reverse the serialization process - // each length is 4 bytes long, we have a total of 8 members - let tcbinfo_bytes_len = u32::from_le_bytes(slice[0..4].try_into().unwrap()) as usize; - let qeidentity_bytes_len = u32::from_le_bytes(slice[4..8].try_into().unwrap()) as usize; - let sgx_intel_root_ca_der_len = u32::from_le_bytes(slice[8..12].try_into().unwrap()) as usize; - let sgx_tcb_signing_der_len = u32::from_le_bytes(slice[12..16].try_into().unwrap()) as usize; - let sgx_pck_certchain_der_len = u32::from_le_bytes(slice[16..20].try_into().unwrap()) as usize; - let sgx_intel_root_ca_crl_der_len = u32::from_le_bytes(slice[20..24].try_into().unwrap()) as usize; - let sgx_pck_processor_crl_der_len = u32::from_le_bytes(slice[24..28].try_into().unwrap()) as usize; - let sgx_pck_platform_crl_der_len = u32::from_le_bytes(slice[28..32].try_into().unwrap()) as usize; - - let mut offset = 4 * 8 as usize; - let tcbinfo_bytes: Option> = match tcbinfo_bytes_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += tcbinfo_bytes_len; - - let qeidentity_bytes: Option> = match qeidentity_bytes_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += qeidentity_bytes_len; - - let sgx_intel_root_ca_der: Option> = match sgx_intel_root_ca_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_intel_root_ca_der_len; - - let sgx_tcb_signing_der: Option> = match sgx_tcb_signing_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_tcb_signing_der_len; - - let sgx_pck_certchain_der: Option> = match sgx_pck_certchain_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_pck_certchain_der_len; - - let sgx_intel_root_ca_crl_der: Option> = match sgx_intel_root_ca_crl_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_intel_root_ca_crl_der_len; - - let sgx_pck_processor_crl_der: Option> = match sgx_pck_processor_crl_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_pck_processor_crl_der_len; - - let sgx_pck_platform_crl_der: Option> = match sgx_pck_platform_crl_der_len { - 0 => None, - len => Some(slice[offset..offset + len].to_vec()) - }; - offset += sgx_pck_platform_crl_der_len; - - assert!(offset == slice.len()); - - IntelCollateral { - tcbinfo_bytes: tcbinfo_bytes, - qeidentity_bytes: qeidentity_bytes, - sgx_intel_root_ca_der, - sgx_tcb_signing_der, - sgx_pck_certchain_der, - sgx_intel_root_ca_crl_der, - sgx_pck_processor_crl_der, - sgx_pck_platform_crl_der, - } - } - - pub fn get_tcbinfov2(&self) -> TcbInfoV2 { - match &self.tcbinfo_bytes { - Some(tcbinfov2) => { - let tcbinfo: TcbInfoV2 = serde_json::from_slice(tcbinfov2).unwrap(); - assert_eq!(tcbinfo.tcb_info.version, 2); - tcbinfo - }, - None => panic!("TCB Info V2 not set"), - } - } - - pub fn get_tcbinfov3(&self) -> TcbInfoV3 { - match &self.tcbinfo_bytes { - Some(tcbinfov3) => { - let tcbinfo: TcbInfoV3 = serde_json::from_slice(tcbinfov3).unwrap(); - assert_eq!(tcbinfo.tcb_info.version, 3); - tcbinfo - }, - None => panic!("TCB Info V3 not set"), - } - } - - pub fn set_tcbinfo_bytes(&mut self, tcbinfo_slice: &[u8]) { - self.tcbinfo_bytes = Some(tcbinfo_slice.to_vec()); - } - - pub fn get_qeidentityv2(&self) -> EnclaveIdentityV2 { - match &self.qeidentity_bytes { - Some(qeidentityv2) => { - let qeidentity = serde_json::from_slice(qeidentityv2).unwrap(); - qeidentity - }, - None => panic!("QE Identity V2 not set"), - } - } - - pub fn set_qeidentity_bytes(&mut self, qeidentity_slice: &[u8]) { - self.qeidentity_bytes = Some(qeidentity_slice.to_vec()); - } - - pub fn get_sgx_intel_root_ca<'a>(&'a self) -> X509Certificate<'a> { - match self.sgx_intel_root_ca_der { - Some(ref der) => { - let cert = parse_x509_der(der); - cert - }, - None => panic!("Intel Root CA not set"), - } - } - - pub fn set_intel_root_ca_der(&mut self, intel_root_ca_der: &[u8]) { - self.sgx_intel_root_ca_der = Some(intel_root_ca_der.to_vec()); - } - - pub fn get_sgx_tcb_signing<'a>(&'a self) -> X509Certificate<'a> { - match self.sgx_tcb_signing_der { - Some(ref der) => { - let cert = parse_x509_der(der); - cert - }, - None => panic!("SGX TCB Signing Cert not set"), - } - } - - pub fn set_sgx_tcb_signing_der(&mut self, sgx_tcb_signing_der: &[u8]) { - self.sgx_tcb_signing_der = Some(sgx_tcb_signing_der.to_vec()); - } - - pub fn set_sgx_tcb_signing_pem(&mut self, sgx_tcb_signing_pem: &[u8]) { - // convert pem to der - let sgx_tcb_signing_der = pem_to_der(sgx_tcb_signing_pem); - self.sgx_tcb_signing_der = Some(sgx_tcb_signing_der); - } - - pub fn get_sgx_pck_certchain<'a>(&'a self) -> Option>> { - match &self.sgx_pck_certchain_der { - Some(certchain_der) => { - let certchain = parse_x509_der_multi(certchain_der); - Some(certchain) - }, - None => None, - } - } - - pub fn set_sgx_pck_certchain_der(&mut self, sgx_pck_certchain_der: Option<&[u8]>) { - match sgx_pck_certchain_der { - Some(certchain_der) => { - self.sgx_pck_certchain_der = Some(certchain_der.to_vec()); - }, - None => { - self.sgx_pck_certchain_der = None; - }, - } - } - - pub fn set_sgx_pck_certchain_pem(&mut self, sgx_pck_certchain_pem: Option<&[u8]>) { - match sgx_pck_certchain_pem { - Some(certchain_pem) => { - // convert pem to der - let sgx_pck_certchain_der = pem_to_der(certchain_pem); - self.sgx_pck_certchain_der = Some(sgx_pck_certchain_der); - }, - None => { - self.sgx_pck_certchain_der = None; - }, - } - } - - pub fn get_sgx_intel_root_ca_crl<'a>(&'a self) -> Option> { - match &self.sgx_intel_root_ca_crl_der { - Some(crl_der) => { - let crl = parse_crl_der(crl_der); - Some(crl) - }, - None => None, - } - } - - pub fn set_sgx_intel_root_ca_crl_der(&mut self, sgx_intel_root_ca_crl_der: &[u8]) { - self.sgx_intel_root_ca_crl_der = Some(sgx_intel_root_ca_crl_der.to_vec()); - } - - pub fn set_sgx_intel_root_ca_crl_pem(&mut self, sgx_intel_root_ca_crl_pem: &[u8]) { - // convert pem to der - let sgx_intel_root_ca_crl_der = pem_to_der(sgx_intel_root_ca_crl_pem); - self.sgx_intel_root_ca_crl_der = Some(sgx_intel_root_ca_crl_der); - } - - pub fn get_sgx_pck_processor_crl<'a>(&'a self) -> Option> { - match &self.sgx_pck_processor_crl_der { - Some(crl_der) => { - let crl = parse_crl_der(crl_der); - Some(crl) - }, - None => None, - } - } - - pub fn set_sgx_processor_crl_der(&mut self, sgx_pck_processor_crl_der: &[u8]) { - self.sgx_pck_processor_crl_der = Some(sgx_pck_processor_crl_der.to_vec()); - } - - pub fn set_sgx_processor_crl_der_pem(&mut self, sgx_pck_processor_crl_pem: &[u8]) { - // convert pem to der - let sgx_pck_processor_crl_der = pem_to_der(sgx_pck_processor_crl_pem); - self.sgx_pck_processor_crl_der = Some(sgx_pck_processor_crl_der); - } - - pub fn get_sgx_pck_platform_crl<'a>(&'a self) -> Option> { - match &self.sgx_pck_platform_crl_der { - Some(crl_der) => { - let crl = parse_crl_der(crl_der); - Some(crl) - }, - None => None, - } - } - - pub fn set_sgx_platform_crl_der(&mut self, sgx_pck_platform_crl_der: &[u8]) { - self.sgx_pck_platform_crl_der = Some(sgx_pck_platform_crl_der.to_vec()); - } - - pub fn set_sgx_platform_crl_der_pem(&mut self, sgx_pck_platform_crl_pem: &[u8]) { - // convert pem to der - let sgx_pck_platform_crl_der = pem_to_der(sgx_pck_platform_crl_pem); - self.sgx_pck_platform_crl_der = Some(sgx_pck_platform_crl_der); - } -} \ No newline at end of file diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index f735636..26a7689 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -1,173 +1,287 @@ +use super::{UInt32LE, tcb_info::TcbStatus}; +use crate::utils::u32_hex; +use crate::utils::borsh_datetime_as_instant; +use crate::utils::borsh_uint32le; +use anyhow::Context; +use chrono::Utc; +use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; +use borsh::{BorshDeserialize, BorshSerialize}; -// EnclaveIdentityV2: -// type: object -// description: SGX Enclave Identity data structure encoded as JSON string in case of success -// (200 HTTP status code) -// properties: -// enclaveIdentity: -// type: object -// properties: -// id: -// type: string -// description: Identifier of the SGX Enclave issued by Intel. Supported values are QE, QVE and TD_QE -// version: -// type: integer -// example: 2 -// description: Version of the structure -// issueDate: -// type: string -// format: date-time -// description: >- -// Representation of date and time the Enclave Identity information -// was created. The time shall be in UTC and the encoding shall -// be compliant to ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ) -// nextUpdate: -// type: string -// format: date-time -// description: >- -// Representation of date and time by which next Enclave Identity -// information will be issued. The time shall be in -// UTC and the encoding shall be compliant to ISO 8601 standard -// (YYYY-MM-DDThh:mm:ssZ) -// tcbEvaluationDataNumber: -// type: integer -// example: 2 -// description: >- -// A monotonically increasing sequence number changed -// when Intel updates the content of the TCB evaluation data -// set: TCB Info, QE Idenity and QVE Identity. The tcbEvaluationDataNumber -// update is synchronized across TCB Info for all flavors of -// SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE -// Identity. This sequence number allows users to easily determine -// when a particular TCB Info/QE Idenity/QVE Identiy superseedes -// another TCB Info/QE Identity/QVE Identity (value: current -// TCB Recovery event number stored in the database). -// miscselect: -// type: string -// pattern: ^[0-9a-fA-F]{8}$ -// example: '00000000' -// description: Base 16-encoded string representing miscselect "golden" value (upon applying mask). -// miscselectMask: -// type: string -// pattern: ^[0-9a-fA-F]{8}$ -// example: '00000000' -// description: Base 16-encoded string representing mask to be applied to miscselect value retrieved from the platform. -// attributes: -// type: string -// pattern: ^[0-9a-fA-F]{32}$ -// example: '00000000000000000000000000000000' -// description: Base 16-encoded string representing attributes "golden" value (upon applying mask). -// attributesMask: -// type: string -// pattern: ^[0-9a-fA-F]{32}$ -// example: '00000000000000000000000000000000' -// description: Base 16-encoded string representing mask to be applied to attributes value retrieved from the platform. -// mrsigner: -// type: string -// pattern: ^[0-9a-fA-F]{64}$ -// example: '0000000000000000000000000000000000000000000000000000000000000000' -// description: Base 16-encoded string representing mrsigner hash. -// isvprodid: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 65535 -// description: Enclave Product ID. -// tcbLevels: -// description: >- -// Sorted list of supported Enclave TCB levels for given -// QVE encoded as a JSON array of Enclave TCB level objects. -// type: array -// items: -// type: object -// properties: -// tcb: -// type: object -// properties: -// isvsvn: -// description: SGX Enclave's ISV SVN -// type: integer -// tcbDate: -// type: string -// format: date-time -// description: >- -// If there are security advisories published by Intel after tcbDate -// that are for issues whose mitigations are currently enforced* by SGX attestation, -// then the value of tcbStatus for the TCB level will not be UpToDate. -// Otherwise (i.e., either no advisories after or not currently enforced), -// the value of tcbStatus for the TCB level will not be OutOfDate. -// -// The time shall be in UTC and the encoding shall -// be compliant to ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). -// tcbStatus: -// type: string -// enum: -// - UpToDate -// - OutOfDate -// - Revoked -// description: >- -// TCB level status. One of the following values: -// -// "UpToDate" - TCB level of the SGX platform is up-to-date. -// -// "OutOfDate" - TCB level of SGX platform is outdated. -// -// "Revoked" - TCB level of SGX platform is revoked. -// The platform is not trustworthy. -// advisoryIDs: -// type: array -// description: >- -// Array of Advisory IDs referring to Intel security advisories that -// provide insight into the reason(s) for the value of tcbStatus for -// this TCB level when the value is not UpToDate. -// -// This field is optional. It will be present only -// if the list of Advisory IDs is not empty. -// items: -// type: string -// signature: -// type: string -// description: Hex-encoded string representation of a signature calculated -// over qeIdentity body (without whitespaces) using TCB Info Signing Key. - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +const ENCLAVE_IDENTITY_V2: u16 = 2; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] #[serde(rename_all = "camelCase")] -pub struct EnclaveIdentityV2 { - pub enclave_identity: EnclaveIdentityV2Inner, - pub signature: String, +pub struct QeTcb { + pub isvsvn: u16, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct QuotingEnclaveIdentityAndSignature { + #[serde(rename = "enclaveIdentity")] + enclave_identity_raw: Box, + #[serde(with = "hex")] + signature: Vec, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +impl QuotingEnclaveIdentityAndSignature { + /// Validate the enclave identity and return the enclave identity if it is valid + /// It checks the signature, the version, and the timestamp. + /// The enclave identities should have their version set to 2. + pub fn validate_as_enclave_identity( + &self, + public_key: &VerifyingKey, + ) -> anyhow::Result { + public_key + .verify( + self.enclave_identity_raw.to_string().as_bytes(), + &Signature::from_slice(&self.signature)?, + ) + .context("Failed to verify enclave identity signature")?; + + let enclave_identity: EnclaveIdentity = + serde_json::from_str(self.enclave_identity_raw.get()) + .context("Failed to deserialize enclave identity")?; + + if enclave_identity.version != ENCLAVE_IDENTITY_V2 { + return Err(anyhow::anyhow!( + "unsupported enclave identity version, only v2 is supported" + )); + } + + Ok(enclave_identity) + } + + pub fn get_enclave_identity_bytes(&self) -> Vec { + self.enclave_identity_raw.to_string().into_bytes() + } + + pub fn get_enclave_identity(&self) -> anyhow::Result { + serde_json::from_str(self.enclave_identity_raw.get()).context("Failed to deserialize enclave identity") + } + + pub fn get_signature_bytes(&self) -> Vec { + self.signature.clone() + } +} + +#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize)] #[serde(rename_all = "camelCase")] -pub struct EnclaveIdentityV2Inner { - pub id: String, - pub version: u32, - pub issue_date: String, - pub next_update: String, - pub tcb_evaluation_data_number: u32, - pub miscselect: String, - pub miscselect_mask: String, - pub attributes: String, - pub attributes_mask: String, - pub mrsigner: String, +pub struct EnclaveIdentity { + /// Identifier of the SGX Enclave issued by Intel. + pub id: EnclaveType, + + /// Version of the structure. + pub version: u16, + + /// The time the Enclave Identity Information was created. The time shalle be in UTC + /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub issue_date: chrono::DateTime, + + /// The time by which next Enclave Identity information will be issued. The time shall be in UTC + /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub next_update: chrono::DateTime, + + /// A monotonically increasing sequence number changed when Intel updates the content of the TCB evaluation data set: + /// TCB Info, QE Identity, QVE Identity. The tcbEvaluationDataNUmber update is synchronized across TCB infor for all + /// flavours of SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE Identity. + /// This sequence number allows users to easily determine when a particular TCB Info/QE Identity/QVE Identity + /// superseedes another TCB Info/QE Identity/QVE Identity (value: current TCB Recovery event number stored in the database). + _tcb_evaluation_data_number: u16, + + /// Base 16-encoded string representing miscselect "golden" value (upon applying mask). + #[serde(with = "u32_hex")] + #[borsh(deserialize_with = "borsh_uint32le::deserialize", serialize_with = "borsh_uint32le::serialize")] + pub miscselect: UInt32LE, + + /// Base 16-encoded string representing mask to be applied to miscselect value retrieved from the platform. + #[serde(with = "u32_hex")] + #[borsh(deserialize_with = "borsh_uint32le::deserialize", serialize_with = "borsh_uint32le::serialize")] + pub miscselect_mask: UInt32LE, + + /// Base 16-encoded string representing attributes "golden" value (upon applying mask). + #[serde(with = "hex")] + pub attributes: [u8; 16], + + /// Base 16-encoded string representing mask to be applied to attributes value retrieved from the platform. + #[serde(with = "hex")] + pub attributes_mask: [u8; 16], + + /// Base 16-encoded string representing mrsigner hash. + #[serde(with = "hex")] + pub mrsigner: [u8; 32], + + /// Enclave Product ID. pub isvprodid: u16, - pub tcb_levels: Vec, + + /// Sorted list of supported Enclave TCB levels for given QVE encoded as a JSON array of Enclave TCB level objects. + pub tcb_levels: Vec, +} + +impl EnclaveIdentity { + pub fn get_qe_tcb_status(&self, isv_svn: u16) -> QeTcbStatus { + self.tcb_levels + .iter() + .find(|level| level.tcb.isvsvn <= isv_svn) + .map(|level| level.tcb_status.clone()) + .unwrap_or(QeTcbStatus::Unspecified) + } } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +/// Enclave TCB level +#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize)] #[serde(rename_all = "camelCase")] -pub struct EnclaveIdentityV2TcbLevelItem { - pub tcb: EnclaveIdentityV2TcbLevel, - pub tcb_date: String, - pub tcb_status: String, - #[serde(rename(serialize = "advisoryIDs", deserialize = "advisoryIDs"))] +pub struct QeTcbLevel { + /// SGX Enclave's ISV SVN + tcb: QeTcb, + /// The time the TCB was evaluated. The time shall be in UTC and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + _tcb_date: chrono::DateTime, + /// TCB level status + tcb_status: QeTcbStatus, + #[serde(rename = "advisoryIDs")] #[serde(skip_serializing_if = "Option::is_none")] pub advisory_ids: Option>, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EnclaveIdentityV2TcbLevel { - pub isvsvn: u16, +/// TCB level status +#[derive(Deserialize, Serialize, Debug, Clone, BorshDeserialize, BorshSerialize)] +pub enum QeTcbStatus { + /// TCB level of the SGX platform is up-to-date. + UpToDate, + /// TCB level of SGX platform requires SW hardening. + SWHardeningNeeded, + /// TCB level of SGX platform is outdated. + OutOfDate, + /// TCB level of SGX platform is outdated and requires a configuration change. + OutOfDateConfigurationNeeded, + /// TCB level of SGX platform is outdated and requires a configuration change. + ConfigurationNeeded, + /// TCB level of SGX platform is outdated and requires a configuration change and SW hardening. + ConfigurationAndSWHardeningNeeded, + /// TCB level of SGX platform is revoked. + Revoked, + /// Unknown TCB level status. + Unspecified, +} + +impl std::fmt::Display for QeTcbStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QeTcbStatus::UpToDate => write!(f, "UpToDate"), + QeTcbStatus::OutOfDate => write!(f, "OutOfDate"), + QeTcbStatus::Revoked => write!(f, "Revoked"), + QeTcbStatus::ConfigurationNeeded => write!(f, "ConfigurationNeeded"), + QeTcbStatus::ConfigurationAndSWHardeningNeeded => write!(f, "ConfigurationAndSWHardeningNeeded"), + QeTcbStatus::SWHardeningNeeded => write!(f, "SWHardeningNeeded"), + QeTcbStatus::OutOfDateConfigurationNeeded => write!(f, "OutOfDateConfigurationNeeded"), + QeTcbStatus::Unspecified => write!(f, "Unspecified"), + } + } +} + +#[allow(clippy::from_over_into)] +impl Into for QeTcbStatus { + fn into(self) -> TcbStatus { + match self { + QeTcbStatus::UpToDate => TcbStatus::UpToDate, + QeTcbStatus::OutOfDate => TcbStatus::OutOfDate, + QeTcbStatus::Revoked => TcbStatus::Revoked, + QeTcbStatus::ConfigurationNeeded => TcbStatus::ConfigurationNeeded, + QeTcbStatus::ConfigurationAndSWHardeningNeeded => { + TcbStatus::ConfigurationAndSWHardeningNeeded + }, + QeTcbStatus::SWHardeningNeeded => TcbStatus::SWHardeningNeeded, + QeTcbStatus::OutOfDateConfigurationNeeded => TcbStatus::OutOfDateConfigurationNeeded, + QeTcbStatus::Unspecified => TcbStatus::Unspecified, + } + } +} + +impl std::str::FromStr for QeTcbStatus { + type Err = anyhow::Error; + + fn from_str(status: &str) -> Result { + match status { + "UpToDate" => Ok(QeTcbStatus::UpToDate), + "OutOfDate" => Ok(QeTcbStatus::OutOfDate), + "Revoked" => Ok(QeTcbStatus::Revoked), + _ => Ok(QeTcbStatus::Unspecified), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum EnclaveType { + /// Quoting Enclave + Qe, + /// Quote Verification Enclave + Qve, + /// TDX Quoting Enclave + #[serde(rename = "TD_QE")] + TdQe, +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_enclave_identity_serialization() { + let qe_identity = include_bytes!("../../data/qeidentityv2_apiv4.json"); + let qe_identity: QuotingEnclaveIdentityAndSignature = serde_json::from_slice(qe_identity).unwrap(); + let original_qe_identity = qe_identity.get_enclave_identity().unwrap(); + + let enclave_identity_bytes = borsh::to_vec(&original_qe_identity).unwrap(); + let deserialized_qe_identity = EnclaveIdentity::try_from_slice(&enclave_identity_bytes).unwrap(); + + // Verify that the deserialized enclave identity matches the original + assert_eq!(original_qe_identity.id, deserialized_qe_identity.id); + assert_eq!(original_qe_identity.version, deserialized_qe_identity.version); + assert_eq!(original_qe_identity.issue_date, deserialized_qe_identity.issue_date); + assert_eq!(original_qe_identity.next_update, deserialized_qe_identity.next_update); + assert_eq!(original_qe_identity.miscselect, deserialized_qe_identity.miscselect); + assert_eq!(original_qe_identity.miscselect_mask, deserialized_qe_identity.miscselect_mask); + assert_eq!(original_qe_identity.attributes, deserialized_qe_identity.attributes); + assert_eq!(original_qe_identity.attributes_mask, deserialized_qe_identity.attributes_mask); + assert_eq!(original_qe_identity.mrsigner, deserialized_qe_identity.mrsigner); + assert_eq!(original_qe_identity.isvprodid, deserialized_qe_identity.isvprodid); + assert_eq!(original_qe_identity.tcb_levels.len(), deserialized_qe_identity.tcb_levels.len()); + + // Detailed verification of each tcb_level + for (i, original_tcb_level) in original_qe_identity.tcb_levels.iter().enumerate() { + let deserialized_tcb_level = &deserialized_qe_identity.tcb_levels[i]; + + // Verify TCB values + assert_eq!(original_tcb_level.tcb.isvsvn, deserialized_tcb_level.tcb.isvsvn); + + // Verify TCB date + assert_eq!(original_tcb_level._tcb_date, deserialized_tcb_level._tcb_date); + + // Verify TCB status + assert!(matches!( + &original_tcb_level.tcb_status, + status if std::mem::discriminant(status) == std::mem::discriminant(&deserialized_tcb_level.tcb_status) + )); + + // Verify advisory IDs + match (&original_tcb_level.advisory_ids, &deserialized_tcb_level.advisory_ids) { + (Some(original_ids), Some(deserialized_ids)) => { + assert_eq!(original_ids.len(), deserialized_ids.len()); + for (j, original_id) in original_ids.iter().enumerate() { + assert_eq!(original_id, &deserialized_ids[j]); + } + }, + (None, None) => {}, + _ => panic!("Advisory IDs mismatch in TCB level {}", i), + } + } + } + + } diff --git a/src/types/mod.rs b/src/types/mod.rs index 3cf055d..89fb773 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,40 +1,16 @@ -use self::quotes::body::*; -use crate::constants::{ENCLAVE_REPORT_LEN, SGX_TEE_TYPE, TD10_REPORT_LEN, TDX_TEE_TYPE}; -use alloy_sol_types::SolValue; -use serde::{Deserialize, Serialize}; +use quote::QuoteBody; +use tcb_info::TcbStatus; -pub mod cert; -pub mod collaterals; +pub mod collateral; pub mod enclave_identity; -pub mod quotes; -pub mod tcbinfo; +pub mod quote; +pub mod report; +pub mod sgx_x509; +pub mod tcb_info; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum TcbStatus { - OK, - TcbSwHardeningNeeded, - TcbConfigurationAndSwHardeningNeeded, - TcbConfigurationNeeded, - TcbOutOfDate, - TcbOutOfDateConfigurationNeeded, - TcbRevoked, - TcbUnrecognized, -} - -impl TcbStatus { - pub fn from_str(s: &str) -> Self { - return match s { - "UpToDate" => TcbStatus::OK, - "SWHardeningNeeded" => TcbStatus::TcbSwHardeningNeeded, - "ConfigurationAndSWHardeningNeeded" => TcbStatus::TcbConfigurationAndSwHardeningNeeded, - "ConfigurationNeeded" => TcbStatus::TcbConfigurationNeeded, - "OutOfDate" => TcbStatus::TcbOutOfDate, - "OutOfDateConfigurationNeeded" => TcbStatus::TcbOutOfDateConfigurationNeeded, - "Revoked" => TcbStatus::TcbRevoked, - _ => TcbStatus::TcbUnrecognized, - }; - } -} +pub type UInt16LE = zerocopy::little_endian::U16; +pub type UInt32LE = zerocopy::little_endian::U32; +pub type UInt64LE = zerocopy::little_endian::U64; // serialization: // [quote_vesion][tee_type][tcb_status][fmspc][quote_body_raw_bytes][abi-encoded string array of tcb_advisory_ids] @@ -49,91 +25,3 @@ pub struct VerifiedOutput { pub quote_body: QuoteBody, pub advisory_ids: Option>, } - -impl VerifiedOutput { - pub fn to_bytes(&self) -> Vec { - let mut output_vec = Vec::new(); - - // big-endian encoded to ensure on-chain compatibility - output_vec.extend_from_slice(&self.quote_version.to_be_bytes()); - - output_vec.extend_from_slice(&self.tee_type.to_le_bytes()); - output_vec.push(match self.tcb_status { - TcbStatus::OK => 0, - TcbStatus::TcbSwHardeningNeeded => 1, - TcbStatus::TcbConfigurationAndSwHardeningNeeded => 2, - TcbStatus::TcbConfigurationNeeded => 3, - TcbStatus::TcbOutOfDate => 4, - TcbStatus::TcbOutOfDateConfigurationNeeded => 5, - TcbStatus::TcbRevoked => 6, - TcbStatus::TcbUnrecognized => 7, - }); - output_vec.extend_from_slice(&self.fmspc); - - match self.quote_body { - QuoteBody::SGXQuoteBody(body) => { - output_vec.extend_from_slice(&body.to_bytes()); - } - QuoteBody::TD10QuoteBody(body) => { - output_vec.extend_from_slice(&body.to_bytes()); - } - } - - if let Some(advisory_ids) = self.advisory_ids.as_ref() { - let encoded = advisory_ids.abi_encode(); - output_vec.extend_from_slice(encoded.as_slice()); - } - - output_vec - } - - pub fn from_bytes(slice: &[u8]) -> VerifiedOutput { - let mut quote_version = [0; 2]; - quote_version.copy_from_slice(&slice[0..2]); - let mut tee_type = [0; 4]; - tee_type.copy_from_slice(&slice[2..6]); - let tcb_status = match slice[6] { - 0 => TcbStatus::OK, - 1 => TcbStatus::TcbSwHardeningNeeded, - 2 => TcbStatus::TcbConfigurationAndSwHardeningNeeded, - 3 => TcbStatus::TcbConfigurationNeeded, - 4 => TcbStatus::TcbOutOfDate, - 5 => TcbStatus::TcbOutOfDateConfigurationNeeded, - 6 => TcbStatus::TcbRevoked, - 7 => TcbStatus::TcbUnrecognized, - _ => panic!("Invalid TCB Status"), - }; - let mut fmspc = [0; 6]; - fmspc.copy_from_slice(&slice[7..13]); - - let mut offset = 13usize; - let quote_body = match u32::from_le_bytes(tee_type) { - SGX_TEE_TYPE => { - let raw_quote_body = &slice[offset..offset + ENCLAVE_REPORT_LEN]; - offset += ENCLAVE_REPORT_LEN; - QuoteBody::SGXQuoteBody(EnclaveReport::from_bytes(raw_quote_body)) - } - TDX_TEE_TYPE => { - let raw_quote_body = &slice[offset..offset + TD10_REPORT_LEN]; - offset += TD10_REPORT_LEN; - QuoteBody::TD10QuoteBody(TD10ReportBody::from_bytes(raw_quote_body)) - } - _ => panic!("unknown TEE type"), - }; - - let mut advisory_ids = None; - if offset < slice.len() { - let advisory_ids_slice = &slice[offset..]; - advisory_ids = Some(>::abi_decode(advisory_ids_slice, true).unwrap()); - } - - VerifiedOutput { - quote_version: u16::from_be_bytes(quote_version), - tee_type: u32::from_le_bytes(tee_type), - tcb_status, - fmspc, - quote_body, - advisory_ids, - } - } -} diff --git a/src/types/quote/body.rs b/src/types/quote/body.rs new file mode 100644 index 0000000..02348b1 --- /dev/null +++ b/src/types/quote/body.rs @@ -0,0 +1,35 @@ +use zerocopy::AsBytes; + +use crate::types::report::{EnclaveReportBody, Td10ReportBody}; + +use super::{SGX_TEE_TYPE, TDX_TEE_TYPE}; + +/// Body of the Quote data structure. +#[derive(Debug)] +pub enum QuoteBody { + SgxQuoteBody(EnclaveReportBody), + Td10QuoteBody(Td10ReportBody), +} + +impl QuoteBody { + pub fn as_bytes(&self) -> &[u8] { + match self { + Self::SgxQuoteBody(body) => body.as_bytes(), + Self::Td10QuoteBody(body) => body.as_bytes(), + } + } + + pub fn tee_type(&self) -> u32 { + match self { + Self::SgxQuoteBody(_) => SGX_TEE_TYPE, + Self::Td10QuoteBody(_) => TDX_TEE_TYPE, + } + } + + pub fn as_tdx_report_body(&self) -> Option<&Td10ReportBody> { + match self { + Self::Td10QuoteBody(body) => Some(body), + _ => None, + } + } +} diff --git a/src/types/quote/cert_data.rs b/src/types/quote/cert_data.rs new file mode 100644 index 0000000..46ce8b1 --- /dev/null +++ b/src/types/quote/cert_data.rs @@ -0,0 +1,117 @@ +use anyhow::{Context, anyhow}; +use p256::ecdsa::Signature; +use x509_cert::certificate::CertificateInner; +use zerocopy::little_endian; + +use crate::{ + types::{report::EnclaveReportBody, sgx_x509::SgxPckExtension}, + utils::{self, cert_chain_processor}, +}; + +#[derive(Debug)] +pub struct QuoteCertData<'a> { + /// Type of cert key + pub cert_key_type: little_endian::U16, + + /// Size of the cert data + pub cert_data_size: little_endian::U32, + + /// Cert data + pub cert_data: &'a [u8], +} + +impl<'a> QuoteCertData<'a> { + pub fn read(bytes: &mut &'a [u8]) -> anyhow::Result { + let cert_key_type = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("incorrect buffer size"))?; + + let cert_data_size = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("incorrect buffer size"))?; + + let cert_data = utils::read_bytes(bytes, cert_data_size.get() as usize); + + Ok(Self { + cert_key_type, + cert_data_size, + cert_data, + }) + } + + pub fn as_pck_cert_chain_data(&self) -> anyhow::Result { + if self.cert_key_type.get() != CertificationKeyType::PckCertChain as u16 { + return Err(anyhow!( + "cannot transform cert data into pck cert chain data" + )); + } + + let cert_data = self.cert_data.strip_suffix(&[0]).unwrap_or(self.cert_data); + let pck_cert_chain = cert_chain_processor::load_pem_chain_bpf_friendly(cert_data) + .context("Failed to parse PCK certificate chain")?; + + let pck_extension = pck_cert_chain + .first() + .context("CertChain")? + .tbs_certificate + .extensions + .as_ref() + .and_then(|extensions| { + extensions + .iter() + .find(|ext| SgxPckExtension::is_pck_ext(ext.extn_id.to_string())) + }) + .ok_or_else(|| anyhow!("PCK Certificate does not contain a SGX Extension"))?; + + let pck_extension = SgxPckExtension::from_der(pck_extension.extn_value.as_bytes()) + .context("PCK Extension")?; + + Ok(PckCertChainData { + pck_cert_chain, + pck_extension, + }) + } + + pub fn get_pck_extension(&self) -> anyhow::Result { + let first_cert = cert_chain_processor::load_first_cert_from_pem_data(self.cert_data) + .context("Failed to parse PCK certificate chain")?; + + let pck_extension = first_cert.tbs_certificate.extensions + .as_ref() + .and_then(|extensions| { + extensions + .iter() + .find(|ext| SgxPckExtension::is_pck_ext(ext.extn_id.to_string())) + }) + .ok_or_else(|| anyhow!("PCK Certificate does not contain a SGX Extension"))?; + + let pck_extension = SgxPckExtension::from_der(pck_extension.extn_value.as_bytes()) + .context("PCK Extension")?; + + Ok(pck_extension) + } +} + +pub struct QuotingEnclaveReportCertData<'a> { + pub qe_report: EnclaveReportBody, + + pub qe_report_signature: Signature, + + pub qe_auth_data: &'a [u8], + + pub pck_cert_chain_data: PckCertChainData, +} + +pub struct PckCertChainData { + pub pck_cert_chain: Vec, + + pub pck_extension: SgxPckExtension, +} + +#[derive(Debug, PartialEq)] +pub enum CertificationKeyType { + _PpidClearText = 1, + _PpidRsa2048Encrypted, + _PpidRsa3072Encrypted, + _PckCleartext, + PckCertChain, + EcdsaSigAuxData, +} diff --git a/src/types/quote/header.rs b/src/types/quote/header.rs new file mode 100644 index 0000000..2b2dd6f --- /dev/null +++ b/src/types/quote/header.rs @@ -0,0 +1,87 @@ +use super::{QUOTE_V3, QUOTE_V4}; +use anyhow::anyhow; +use zerocopy::little_endian; + +pub const INTEL_QE_VENDOR_ID: [u8; 16] = [ + 0x93, 0x9A, 0x72, 0x33, 0xF7, 0x9C, 0x4C, 0xA9, 0x94, 0x0A, 0x0D, 0xB3, 0x95, 0x7F, 0x06, 0x07, +]; + +/// Header of the SGX Quote data structure. +/// +/// We use zerocopy for zero-copy parsing of the quote header from raw bytes. +/// This allows us to safely interpret the raw byte slice as a structured type without copying the data. +/// Benefits: +/// +/// 1. Performance: Avoids memory allocation and copying of bytes +/// 2. Safety: Ensures the struct layout is compatible with the raw bytes through compile-time checks +/// 3. Direct memory mapping: Can read directly from memory-mapped files or network buffers +/// +/// The FromBytes trait ensures the type is safe to interpret from any byte sequence +/// The FromZeroes trait ensures the type is safe to create from zero bytes +#[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] +#[repr(C)] +pub struct QuoteHeader { + /// Version of the quote data structure. + /// (0) + pub version: little_endian::U16, + + /// Type of attestation key used by the quoting enclave. + /// 2 (ECDSA-256-with-P-256 curve) + /// 3 (ECDSA-384-with-P-384 curve) + /// (2) + pub attestation_key_type: little_endian::U16, + + /// TEE for this Attestation + /// 0x00000000: SGX + /// 0x00000081: TDX + /// (4) + pub tee_type: u32, + + /// Security Version of the Quoting Enclave + /// (8) + pub qe_svn: little_endian::U16, + + /// Security Version of the PCE - 0 (Only applicable for SGX Quotes) + /// (10) + pub pce_svn: little_endian::U16, + + /// Unique identifier of the QE Vendor. + /// Value: 939A7233F79C4CA9940A0DB3957F0607 (Intel® SGX QE Vendor) + /// (12) + pub qe_vendor_id: [u8; 16], + + /// Custom user-defined data. For the Intel® SGX and TDX DCAP Quote Generation Libraries, + /// the first 16 bytes contain a Platform Identifier that is used to link a PCK Certificate to an Enc(PPID). + /// (28) + pub user_data: [u8; 20], + // Total size: 48 bytes +} + +impl TryFrom<[u8; std::mem::size_of::()]> for QuoteHeader { + type Error = anyhow::Error; + + fn try_from(value: [u8; std::mem::size_of::()]) -> Result { + let quote_header = + ::read_from(&value).expect("failed to read quote header"); + + if quote_header.version.get() != QUOTE_V3 && quote_header.version.get() != QUOTE_V4 { + return Err(anyhow!("unsupported quote version")); + } + + if quote_header.attestation_key_type.get() != AttestationKeyType::Ecdsa256P256 as u16 { + return Err(anyhow!("unsupported attestation key type")); + } + + if quote_header.qe_vendor_id != INTEL_QE_VENDOR_ID { + return Err(anyhow!("unsupported qe vendor id")); + } + + Ok(quote_header) + } +} + +/// Attestation Key Type +pub enum AttestationKeyType { + Ecdsa256P256 = 2, + Ecdsa384P384 = 3, +} diff --git a/src/types/quote/mod.rs b/src/types/quote/mod.rs new file mode 100644 index 0000000..fb521c7 --- /dev/null +++ b/src/types/quote/mod.rs @@ -0,0 +1,71 @@ +mod body; +mod cert_data; +mod header; +mod signature; + +use anyhow::anyhow; +pub use body::*; +pub use cert_data::*; +pub use header::*; +pub use signature::*; + +use crate::utils; + +use super::report::{EnclaveReportBody, Td10ReportBody}; + +#[allow(non_snake_case)] +const QUOTE_V3: u16 = 3; +#[allow(non_snake_case)] +const QUOTE_V4: u16 = 4; + +pub const SGX_TEE_TYPE: u32 = 0x00000000; +pub const TDX_TEE_TYPE: u32 = 0x00000081; + +/// A DCAP quote, used for verification. +#[derive(Debug)] +pub struct Quote<'a> { + /// Header of the SGX Quote data structure. + pub header: QuoteHeader, + + /// Software Vendor enclave report. + pub body: QuoteBody, + + /// Signature of the quote body. + pub signature: QuoteSignatureData<'a>, +} + +impl<'a> Quote<'a> { + pub fn read(bytes: &mut &'a [u8]) -> anyhow::Result { + if bytes.len() < std::mem::size_of::() { + return Err(anyhow!("incorrect buffer size")); + } + + // Read the quote header + let quote_header = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading quote header"))?; + + // Read the quote body and signature + if quote_header.tee_type == SGX_TEE_TYPE { + let quote_body = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; + let quote_signature = QuoteSignatureData::read(bytes, quote_header.version.get())?; + Ok(Quote { + header: quote_header, + body: QuoteBody::SgxQuoteBody(quote_body), + signature: quote_signature, + }) + } else if quote_header.tee_type == TDX_TEE_TYPE { + let quote_body = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading td10 report body"))?; + let quote_signature = QuoteSignatureData::read(bytes, quote_header.version.get())?; + + return Ok(Quote { + header: quote_header, + body: QuoteBody::Td10QuoteBody(quote_body), + signature: quote_signature, + }); + } else { + return Err(anyhow!("unsupported quote version")); + } + } +} diff --git a/src/types/quote/signature.rs b/src/types/quote/signature.rs new file mode 100644 index 0000000..c48b154 --- /dev/null +++ b/src/types/quote/signature.rs @@ -0,0 +1,169 @@ +use anyhow::{anyhow, bail}; +use sha2::{Digest, Sha256}; +use zerocopy::little_endian; + +use crate::{ + types::{quote::QuoteCertData, report::EnclaveReportBody, sgx_x509::SgxPckExtension}, + utils, +}; + +use super::{CertificationKeyType, PckCertChainData}; + +/// Signature data for SGX Quotes +/// +/// In the intel docs, this is A 4.4: "ECDSA 2560bit Quote Signature Data Structure" +/// +/// This can be used to validate that the quoting enclave itself is valid, and then that +/// the quoting enclave has signed the ISV enclave report. +#[derive(Debug)] +pub struct QuoteSignatureData<'a> { + /// Signature of the report header + report by the attestation key. + pub isv_signature: &'a [u8], + + /// The public key used to generate the isv_signature. + pub attestation_pub_key: &'a [u8], + + /// Report of the quoting enclave. + pub qe_report_body: EnclaveReportBody, + + /// Signature of the quoting enclave report using the PCK cert key. + pub qe_report_signature: &'a [u8], + + /// Auth data for the quote + pub auth_data: &'a [u8], + + /// Cert data + pub cert_data: QuoteCertData<'a>, +} + +impl<'a> QuoteSignatureData<'a> { + pub fn read(bytes: &mut &'a [u8], version: u16) -> anyhow::Result { + let signature_len = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading signature length"))? + .get(); + + if bytes.len() < signature_len as usize { + return Err(anyhow!("underflow reading signature")); + } + + match version { + 3 => Self::read_v3_signature(bytes), + 4 => Self::read_v4_signature(bytes), + _ => Err(anyhow!("unsupported quote version")), + } + } + + fn read_v3_signature(bytes: &mut &'a [u8]) -> anyhow::Result { + let isv_signature = utils::read_bytes(bytes, 64); + + let attestation_pub_key = utils::read_bytes(bytes, 64); + + let qe_report_body = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; + + let qe_report_signature = utils::read_bytes(bytes, 64); + + let auth_data_size = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("Failed to read auth data size"))? + .get(); + + if bytes.len() < auth_data_size as usize { + return Err(anyhow!("buffer underflow")); + } + + let auth_data = utils::read_bytes(bytes, auth_data_size as usize); + let cert_data = QuoteCertData::read(bytes)?; + + Ok(QuoteSignatureData { + isv_signature, + attestation_pub_key, + qe_report_body, + qe_report_signature, + auth_data, + cert_data, + }) + } + + fn read_v4_signature(bytes: &mut &'a [u8]) -> anyhow::Result { + let isv_signature = utils::read_bytes(bytes, 64); + + let attestation_pub_key = utils::read_bytes(bytes, 64); + + let mut cert_data_struct = QuoteCertData::read(bytes)?; + + if cert_data_struct.cert_key_type.get() != CertificationKeyType::EcdsaSigAuxData as u16 { + return Err(anyhow!( + "cannot transform cert data into quoting enclave report cert data" + )); + } + + // Parse the QE report + let qe_report_body = + utils::read_from_bytes::(&mut cert_data_struct.cert_data) + .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; + + // Parse the QE report signature + let qe_report_signature = utils::read_bytes(&mut cert_data_struct.cert_data, 64); + + // Read auth data size and auth data + let auth_data_size = + utils::read_from_bytes::(&mut cert_data_struct.cert_data) + .ok_or_else(|| anyhow!("Failed to read auth data size"))?; + + if cert_data_struct.cert_data.len() < auth_data_size.get() as usize { + return Err(anyhow!("buffer underflow")); + } + + let qe_auth_data = utils::read_bytes( + &mut cert_data_struct.cert_data, + auth_data_size.get() as usize, + ); + let cert_data = QuoteCertData::read(&mut cert_data_struct.cert_data)?; + + Ok(QuoteSignatureData { + isv_signature, + attestation_pub_key, + qe_report_body, + qe_report_signature, + auth_data: qe_auth_data, + cert_data, + }) + } + + pub fn get_pck_cert_chain(&self) -> anyhow::Result { + self.cert_data.as_pck_cert_chain_data() + } + + pub fn get_pck_extension(&self) -> anyhow::Result { + self.cert_data.get_pck_extension() + } + + /// Verfiy the report generated by the quoting enclave. + /// + /// By specification, the quoting enclave report data `sgx_report_data_bytes` must b e + /// SHA256(ECDSA Attestation Key || QE Authentication Data) || 32- 0x00s + pub fn verify_qe_report(&self) -> anyhow::Result<()> { + let mut hasher = Sha256::new(); + + hasher.update(self.attestation_pub_key); + hasher.update(self.auth_data); + let digest = hasher.finalize(); + assert_eq!(digest.len(), 32); + + if *digest != self.qe_report_body.user_report_data[..digest.len()] { + bail!("Quoting enclave report should be hash of attestation key and auth data"); + } + + if self.qe_report_body.user_report_data[digest.len()..] != [0; 32] { + bail!("Quoting enclave report should be 32 zero bytes padded"); + } + + Ok(()) + } +} + +#[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes)] +pub struct EcdsaSignatureHeader { + pub isv_signature: [u8; 64], + pub attestation_pub_key: [u8; 64], +} diff --git a/src/types/quotes/body.rs b/src/types/quotes/body.rs deleted file mode 100644 index 5ddc121..0000000 --- a/src/types/quotes/body.rs +++ /dev/null @@ -1,253 +0,0 @@ -#[derive(Debug, Copy, Clone)] -pub enum QuoteBody { - SGXQuoteBody(EnclaveReport), - TD10QuoteBody(TD10ReportBody) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct EnclaveReport { - pub cpu_svn: [u8; 16], // [16 bytes] - // Security Version of the CPU (raw value) - pub misc_select: [u8; 4], // [4 bytes] - // SSA Frame extended feature set. - // Reports what SECS.MISCSELECT settings are used in the enclave. You can limit the - // allowed MISCSELECT settings in the sigstruct using MISCSELECT/MISCMASK. - pub reserved_1: [u8; 28], // [28 bytes] - // Reserved for future use - 0 - pub attributes: [u8; 16], // [16 bytes] - // Set of flags describing attributes of the enclave. - // Reports what SECS.ATTRIBUTES settings are used in the enclave. The ISV can limit what - // SECS.ATTRIBUTES can be used when loading the enclave through parameters to the SGX Signtool. - // The Signtool will produce a SIGSTRUCT with ATTRIBUTES and ATTRIBUTESMASK - // which determine allowed ATTRIBUTES. - // - For each SIGSTRUCT.ATTRIBUTESMASK bit that is set, then corresponding bit in the - // SECS.ATTRIBUTES must match the same bit in SIGSTRUCT.ATTRIBUTES. - pub mrenclave: [u8; 32], // [32 bytes] - // Measurement of the enclave. - // The MRENCLAVE value is the SHA256 hash of the ENCLAVEHASH field in the SIGSTRUCT. - pub reserved_2: [u8; 32], // [32 bytes] - // Reserved for future use - 0 - pub mrsigner: [u8; 32], // [32 bytes] - // Measurement of the enclave signer. - // The MRSIGNER value is the SHA256 hash of the MODULUS field in the SIGSTRUCT. - pub reserved_3: [u8; 96], // [96 bytes] - // Reserved for future use - 0 - pub isv_prod_id: u16, // [2 bytes] - // Product ID of the enclave. - // The ISV should configure a unique ISVProdID for each product which may - // want to share sealed data between enclaves signed with a specific MRSIGNER. The ISV - // may want to supply different data to identical enclaves signed for different products. - pub isv_svn: u16, // [2 bytes] - // Security Version of the enclave - pub reserved_4: [u8; 60], // [60 bytes] - // Reserved for future use - 0 - pub report_data: [u8; 64], // [64 bytes] - // Additional report data. - // The enclave is free to provide 64 bytes of custom data to the REPORT. - // This can be used to provide specific data from the enclave or it can be used to hold - // a hash of a larger block of data which is provided with the quote. - // The verification of the quote signature confirms the integrity of the - // report data (and the rest of the REPORT body). -} - -impl EnclaveReport { - pub fn from_bytes(raw_bytes: &[u8]) -> EnclaveReport{ - assert_eq!(raw_bytes.len(), 384); - let mut obj = EnclaveReport { - cpu_svn: [0; 16], - misc_select: [0; 4], - reserved_1: [0; 28], - attributes: [0; 16], - mrenclave: [0; 32], - reserved_2: [0; 32], - mrsigner: [0; 32], - reserved_3: [0; 96], - isv_prod_id: 0, - isv_svn: 0, - reserved_4: [0; 60], - report_data: [0; 64], - }; - - // parse raw bytes into obj - obj.cpu_svn.copy_from_slice(&raw_bytes[0..16]); - obj.misc_select.copy_from_slice(&raw_bytes[16..20]); - obj.reserved_1.copy_from_slice(&raw_bytes[20..48]); - obj.attributes.copy_from_slice(&raw_bytes[48..64]); - obj.mrenclave.copy_from_slice(&raw_bytes[64..96]); - obj.reserved_2.copy_from_slice(&raw_bytes[96..128]); - obj.mrsigner.copy_from_slice(&raw_bytes[128..160]); - obj.reserved_3.copy_from_slice(&raw_bytes[160..256]); - obj.isv_prod_id = u16::from_le_bytes([raw_bytes[256], raw_bytes[257]]); - obj.isv_svn = u16::from_le_bytes([raw_bytes[258], raw_bytes[259]]); - obj.reserved_4.copy_from_slice(&raw_bytes[260..320]); - obj.report_data.copy_from_slice(&raw_bytes[320..384]); - - return obj; - } - - pub fn to_bytes(&self) -> [u8; 384] { - // convert the struct into raw bytes - let mut raw_bytes = [0; 384]; - // copy the fields into the raw bytes - raw_bytes[0..16].copy_from_slice(&self.cpu_svn); - raw_bytes[16..20].copy_from_slice(&self.misc_select); - raw_bytes[20..48].copy_from_slice(&self.reserved_1); - raw_bytes[48..64].copy_from_slice(&self.attributes); - raw_bytes[64..96].copy_from_slice(&self.mrenclave); - raw_bytes[96..128].copy_from_slice(&self.reserved_2); - raw_bytes[128..160].copy_from_slice(&self.mrsigner); - raw_bytes[160..256].copy_from_slice(&self.reserved_3); - raw_bytes[256..258].copy_from_slice(&self.isv_prod_id.to_le_bytes()); - raw_bytes[258..260].copy_from_slice(&self.isv_svn.to_le_bytes()); - raw_bytes[260..320].copy_from_slice(&self.reserved_4); - raw_bytes[320..384].copy_from_slice(&self.report_data); - - raw_bytes - } -} - -// TD Attributes: -// [bits] : [description] -// [0:7] : (TUD) TD Under Debug flags. -// If any of the bits in this group are set to 1, the TD is untrusted. -// [0] - (DEBUG) Defines whether the TD runs in TD debug mode (set to 1) or not (set to 0). -// In TD debug mode, the CPU state and private memory are accessible by the host VMM. -// [1:7] - (RESERVED) Reserved for future TUD flags, must be 0. -// [8:31] : (SEC) Attributes that may impact the security of the TD -// [8:27] - (RESERVED) Reserved for future SEC flags, must be 0. -// [28] - (SEPT_VE_DISABLE) Disable EPT violation conversion to #VE on TD access of PENDING pages -// [29] - (RESERVED) Reserved for future SEC flags, must be 0. -// [30] - (PKS) TD is allowed to use Supervisor Protection Keys. -// [31] - (KL) TD is allowed to use Key Locker. -// [32:63] : (OTHER) Attributes that do not impact the security of the TD -// [32:62] - (RESERVED) Reserved for future OTHER flags, must be 0. -// [63] - (PERFMON) TD is allowed to use Perfmon and PERF_METRICS capabilities. - -// TEE_TCB_SVN: -// [bytes] : [Name] : [description] -// [0] : Tdxtcbcomp01 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[0] -// [1] : Tdxtcbcomp02 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[1] -// [2] : Tdxtcbcomp03 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[2 -// [3] : Tdxtcbcomp04 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[3] -// [4] : Tdxtcbcomp05 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[4] -// [5] : Tdxtcbcomp06 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[5] -// [6] : Tdxtcbcomp07 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[6] -// [7] : Tdxtcbcomp08 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[7] -// [8] : Tdxtcbcomp09 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[8] -// [9] : Tdxtcbcomp10 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[9] -// [10] : Tdxtcbcomp11 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[10] -// [11] : Tdxtcbcomp12 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[11] -// [12] : Tdxtcbcomp13 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[12] -// [13] : Tdxtcbcomp14 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[13] -// [14] : Tdxtcbcomp15 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[14] -// [15] : Tdxtcbcomp16 : QVL compares with TCBInfo.TCBLevels.tcb.tdxtcbcomponents.svn[15] - -#[derive(Copy, Clone, Debug)] -pub struct TD10ReportBody { - pub tee_tcb_svn: [u8; 16], // [16 bytes] - // Describes the TCB of TDX. (Refer to above) - pub mrseam: [u8; 48], // [48 bytes] - // Measurement of the TDX Module. - pub mrsignerseam: [u8; 48], // [48 bytes] - // Zero for Intel TDX Module - pub seam_attributes: u64, // [8 bytes] - // Must be zero for TDX 1.0 - pub td_attributes: u64, // [8 bytes] - // TD Attributes (Refer to above) - pub xfam: u64, // [8 bytes] - // XFAM (eXtended Features Available Mask) is defined as a 64b bitmap, which has the same format as XCR0 or IA32_XSS MSR. - pub mrtd: [u8; 48], // [48 bytes] - // (SHA384) Measurement of the initial contents of the TD. - pub mrconfigid: [u8; 48], // [48 bytes] - // Software-defined ID for non-owner-defined configuration of the TD, e.g., runtime or OS configuration. - pub mrowner: [u8; 48], // [48 bytes] - // Software-defined ID for the TD’s owner - pub mrownerconfig: [u8; 48], // [48 bytes] - // Software-defined ID for owner-defined configuration of the TD, - // e.g., specific to the workload rather than the runtime or OS. - pub rtmr0: [u8; 48], // [48 bytes] - // (SHA384) Root of Trust for Measurement (RTM) for the TD. - pub rtmr1: [u8; 48], // [48 bytes] - // (SHA384) Root of Trust for Measurement (RTM) for the TD. - pub rtmr2: [u8; 48], // [48 bytes] - // (SHA384) Root of Trust for Measurement (RTM) for the TD. - pub rtmr3: [u8; 48], // [48 bytes] - // (SHA384) Root of Trust for Measurement (RTM) for the TD. - pub report_data: [u8; 64], // [64 bytes] - // Additional report data. - // The TD is free to provide 64 bytes of custom data to the REPORT. - // This can be used to provide specific data from the TD or it can be used to hold a hash of a larger block of data which is provided with the quote. - // Note that the signature of a TD Quote covers the REPORTDATA field. As a result, the integrity is protected with a key rooted in an Intel CA. -} - -impl TD10ReportBody { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - // copy the bytes into the struct - let mut tee_tcb_svn = [0; 16]; - tee_tcb_svn.copy_from_slice(&raw_bytes[0..16]); - let mut mrseam = [0; 48]; - mrseam.copy_from_slice(&raw_bytes[16..64]); - let mut mrsignerseam = [0; 48]; - mrsignerseam.copy_from_slice(&raw_bytes[64..112]); - let seam_attributes = u64::from_le_bytes([raw_bytes[112], raw_bytes[113], raw_bytes[114], raw_bytes[115], raw_bytes[116], raw_bytes[117], raw_bytes[118], raw_bytes[119]]); - let td_attributes = u64::from_le_bytes([raw_bytes[120], raw_bytes[121], raw_bytes[122], raw_bytes[123], raw_bytes[124], raw_bytes[125], raw_bytes[126], raw_bytes[127]]); - let xfam = u64::from_le_bytes([raw_bytes[128], raw_bytes[129], raw_bytes[130], raw_bytes[131], raw_bytes[132], raw_bytes[133], raw_bytes[134], raw_bytes[135]]); - let mut mrtd = [0; 48]; - mrtd.copy_from_slice(&raw_bytes[136..184]); - let mut mrconfigid = [0; 48]; - mrconfigid.copy_from_slice(&raw_bytes[184..232]); - let mut mrowner = [0; 48]; - mrowner.copy_from_slice(&raw_bytes[232..280]); - let mut mrownerconfig = [0; 48]; - mrownerconfig.copy_from_slice(&raw_bytes[280..328]); - let mut rtmr0 = [0; 48]; - rtmr0.copy_from_slice(&raw_bytes[328..376]); - let mut rtmr1 = [0; 48]; - rtmr1.copy_from_slice(&raw_bytes[376..424]); - let mut rtmr2 = [0; 48]; - rtmr2.copy_from_slice(&raw_bytes[424..472]); - let mut rtmr3 = [0; 48]; - rtmr3.copy_from_slice(&raw_bytes[472..520]); - let mut report_data = [0; 64]; - report_data.copy_from_slice(&raw_bytes[520..584]); - - TD10ReportBody { - tee_tcb_svn, - mrseam, - mrsignerseam, - seam_attributes, - td_attributes, - xfam, - mrtd, - mrconfigid, - mrowner, - mrownerconfig, - rtmr0, - rtmr1, - rtmr2, - rtmr3, - report_data, - } - } - - pub fn to_bytes(&self) -> [u8; 584] { - let mut raw_bytes = [0; 584]; - raw_bytes[0..16].copy_from_slice(&self.tee_tcb_svn); - raw_bytes[16..64].copy_from_slice(&self.mrseam); - raw_bytes[64..112].copy_from_slice(&self.mrsignerseam); - raw_bytes[112..120].copy_from_slice(&self.seam_attributes.to_le_bytes()); - raw_bytes[120..128].copy_from_slice(&self.td_attributes.to_le_bytes()); - raw_bytes[128..136].copy_from_slice(&self.xfam.to_le_bytes()); - raw_bytes[136..184].copy_from_slice(&self.mrtd); - raw_bytes[184..232].copy_from_slice(&self.mrconfigid); - raw_bytes[232..280].copy_from_slice(&self.mrowner); - raw_bytes[280..328].copy_from_slice(&self.mrownerconfig); - raw_bytes[328..376].copy_from_slice(&self.rtmr0); - raw_bytes[376..424].copy_from_slice(&self.rtmr1); - raw_bytes[424..472].copy_from_slice(&self.rtmr2); - raw_bytes[472..520].copy_from_slice(&self.rtmr3); - raw_bytes[520..584].copy_from_slice(&self.report_data); - - raw_bytes - } -} \ No newline at end of file diff --git a/src/types/quotes/mod.rs b/src/types/quotes/mod.rs deleted file mode 100644 index 30f9d43..0000000 --- a/src/types/quotes/mod.rs +++ /dev/null @@ -1,183 +0,0 @@ -use super::cert::Certificates; - -pub mod version_3; -pub mod version_4; -pub mod body; - -use body::EnclaveReport; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct QuoteHeader { - pub version: u16, // [2 bytes] - // Version of the quote data structure - 4, 5 - pub att_key_type: u16, // [2 bytes] - // Type of the Attestation Key used by the Quoting Enclave - - // 2 (ECDSA-256-with-P-256 curve) - // 3 (ECDSA-384-with-P-384 curve) - pub tee_type: u32, // [4 bytes] - // TEE for this Attestation - // 0x00000000: SGX - // 0x00000081: TDX - pub qe_svn: [u8; 2], // [2 bytes] - // Security Version of the Quoting Enclave - 1 (only applicable for SGX Quotes) - pub pce_svn: [u8; 2], // [2 bytes] - // Security Version of the PCE - 0 (only applicable for SGX Quotes) - pub qe_vendor_id: [u8; 16], // [16 bytes] - // Unique identifier of the QE Vendor. - // Value: 939A7233F79C4CA9940A0DB3957F0607 (Intel® SGX QE Vendor) - // Note: Each vendor that decides to provide a customized Quote data structure should have - // unique ID. - pub user_data: [u8; 20], // [20 bytes] - // Custom user-defined data. For the Intel® SGX and TDX DCAP Quote Generation Libraries, - // the first 16 bytes contain a Platform Identifier that is used to link a PCK Certificate to an Enc(PPID). -} - -impl QuoteHeader { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - let version = u16::from_le_bytes([raw_bytes[0], raw_bytes[1]]); - let att_key_type = u16::from_le_bytes([raw_bytes[2], raw_bytes[3]]); - let tee_type = u32::from_le_bytes([raw_bytes[4], raw_bytes[5], raw_bytes[6], raw_bytes[7]]); - let mut qe_svn = [0; 2]; - qe_svn.copy_from_slice(&raw_bytes[8..10]); - let mut pce_svn = [0; 2]; - pce_svn.copy_from_slice(&raw_bytes[10..12]); - let mut qe_vendor_id = [0; 16]; - qe_vendor_id.copy_from_slice(&raw_bytes[12..28]); - let mut user_data = [0; 20]; - user_data.copy_from_slice(&raw_bytes[28..48]); - - QuoteHeader { - version, - att_key_type, - tee_type, - qe_svn, - pce_svn, - qe_vendor_id, - user_data, - } - } - - pub fn to_bytes(&self) -> [u8; 48] { - let mut raw_bytes = [0; 48]; - raw_bytes[0..2].copy_from_slice(&self.version.to_le_bytes()); - raw_bytes[2..4].copy_from_slice(&self.att_key_type.to_le_bytes()); - raw_bytes[4..8].copy_from_slice(&self.tee_type.to_le_bytes()); - raw_bytes[8..10].copy_from_slice(&self.qe_svn); - raw_bytes[10..12].copy_from_slice(&self.pce_svn); - raw_bytes[12..28].copy_from_slice(&self.qe_vendor_id); - raw_bytes[28..48].copy_from_slice(&self.user_data); - - raw_bytes - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct QeAuthData { - pub size: u16, - pub data: Vec, -} - -impl QeAuthData { - pub fn from_bytes(raw_bytes: &[u8]) -> QeAuthData { - let size = u16::from_le_bytes([raw_bytes[0], raw_bytes[1]]); - let data = raw_bytes[2..2+size as usize].to_vec(); - QeAuthData { - size, - data, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CertData { - pub cert_data_type: u16, // [2 bytes] - // Determines type of data required to verify the QE Report Signature in the Quote Signature Data structure. - // 1 - (PCK identifier: PPID in plain text, CPUSVN, and PCESVN) - // 2 - (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN, and PCESVN) - // 3 - (PCK identifier: PPID encrypted using RSA-2048-OAEP, CPUSVN, PCESVN, and QEID) - // 4 - (PCK Leaf Certificate in plain text; currently not supported) - // 5 - (Concatenated PCK Cert Chain) - // 6 - (QE Report Certification Data) - // 7 - (PLATFORM_MANIFEST; currently not supported) - pub cert_data_size: u32, // [4 bytes] - // Size of Certification Data field. - pub cert_data: Vec, // [variable bytes] - // Data required to verify the QE Report Signature depending on the value of the Certification Data Type: - // 1: Byte array that contains concatenation of PPID, CPUSVN, PCESVN (LE), PCEID (LE). - // 2: Byte array that contains concatenation of PPID encrypted using RSA-2048-OAEP, CPUSVN, PCESVN (LE), PCEID (LE). - // 3: Byte array that contains concatenation of PPID encrypted using RSA-3072-OAEP, CPUSVN, PCESVN (LE), PCEID (LE). - // 4: PCK Leaf Certificate - // 5: Concatenated PCK Cert Chain (PEM formatted). PCK Leaf Cert || Intermediate CA Cert || Root CA Cert - // 6: QE Report Certification Data - // 7: PLATFORM_MANIFEST -} - -impl CertData { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - let cert_data_type = u16::from_le_bytes([raw_bytes[0], raw_bytes[1]]); - let cert_data_size = u32::from_le_bytes([raw_bytes[2], raw_bytes[3], raw_bytes[4], raw_bytes[5]]); - let cert_data = raw_bytes[6..6+cert_data_size as usize].to_vec(); - - CertData { - cert_data_type, - cert_data_size, - cert_data, - } - } - - pub fn get_cert_data(&self) -> CertDataType { - match self.cert_data_type { - 1 => CertDataType::Type1(self.cert_data.clone()), - 2 => CertDataType::Type2(self.cert_data.clone()), - 3 => CertDataType::Type3(self.cert_data.clone()), - 4 => CertDataType::Type4(self.cert_data.clone()), - 5 => CertDataType::CertChain(Certificates::from_pem(&self.cert_data)), - 6 => CertDataType::QeReportCertData(QeReportCertData::from_bytes(&self.cert_data)), - 7 => CertDataType::Type7(self.cert_data.clone()), - _ => CertDataType::Unused, - } - } -} - -pub enum CertDataType { - Unused, - Type1(Vec), - Type2(Vec), - Type3(Vec), - Type4(Vec), - CertChain(Certificates), - QeReportCertData(QeReportCertData), - Type7(Vec), -} - -#[derive(Clone, Debug)] -pub struct QeReportCertData { - pub qe_report: EnclaveReport, - pub qe_report_signature: [u8; 64], - pub qe_auth_data: QeAuthData, - pub qe_cert_data: CertData, -} - -impl QeReportCertData { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - // 384 bytes for qe_report - let qe_report = EnclaveReport::from_bytes(&raw_bytes[0..384]); - // 64 bytes for qe_report_signature - let mut qe_report_signature = [0; 64]; - qe_report_signature.copy_from_slice(&raw_bytes[384..448]); - // qe auth data is variable length, we'll pass remaining bytes to the from_bytes method - let qe_auth_data = QeAuthData::from_bytes(&raw_bytes[448..]); - // get the length of qe_auth_data - let qe_auth_data_size = 2 + qe_auth_data.size as usize; - // finish off with the parsing of qe_cert_data - let qe_cert_data_start = 448 + qe_auth_data_size; - let qe_cert_data = CertData::from_bytes(&raw_bytes[qe_cert_data_start..]); - - QeReportCertData { - qe_report, - qe_report_signature, - qe_auth_data, - qe_cert_data, - } - } -} \ No newline at end of file diff --git a/src/types/quotes/version_3.rs b/src/types/quotes/version_3.rs deleted file mode 100644 index 28bf948..0000000 --- a/src/types/quotes/version_3.rs +++ /dev/null @@ -1,77 +0,0 @@ -// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_ECDSA_QuoteLibReference_DCAP_API.pdf - -use super::{QuoteHeader, QeAuthData, CertData, body::EnclaveReport}; - -// high level sgx quote structure -// [48 - header] [384 - isv enclave report] [4 - quote signature length] [var - quote signature] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct QuoteV3 { - pub header: QuoteHeader, // [48 bytes] - // Header of Quote data structure. This field is transparent (the user knows - // its internal structure). Rest of the Quote data structure can be - // treated as opaque (hidden from the user). - pub isv_enclave_report: EnclaveReport, // [384 bytes] - // Report of the attested ISV Enclave. - // The CPUSVN and ISVSVN is the TCB when the quote is generated. - // The REPORT.ReportData is defined by the ISV but should provide quote replay - // protection if required. - pub signature_len: u32, // [4 bytes] - // Size of the Quote Signature Data structure in bytes. - pub signature: QuoteSignatureDataV3, // [variable bytes] - // Variable-length data containing the signature and supporting data. - // E.g. ECDSA 256-bit Quote Signature Data Structure (SgxQuoteSignatureData) -} - -impl QuoteV3 { - pub fn from_bytes(raw_bytes: &[u8]) -> QuoteV3 { - let header = QuoteHeader::from_bytes(&raw_bytes[0..48]); - let isv_enclave_report = EnclaveReport::from_bytes(&raw_bytes[48..432]); - let signature_len = u32::from_le_bytes([raw_bytes[432], raw_bytes[433], raw_bytes[434], raw_bytes[435]]); - // allocate and create a buffer for signature - let signature_slice = &raw_bytes[436..436 + signature_len as usize]; - let signature = QuoteSignatureDataV3::from_bytes(signature_slice); - - QuoteV3 { - header, - isv_enclave_report, - signature_len, - signature, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct QuoteSignatureDataV3 { - pub isv_enclave_report_signature: [u8; 64], // ECDSA signature, the r component followed by the s component, 2 x 32 bytes. - pub ecdsa_attestation_key: [u8; 64], // EC KT-I Public Key, the x-coordinate followed by the y-coordinate - // (on the RFC 6090 P-256 curve), 2 x 32 bytes. - pub qe_report: EnclaveReport, - pub qe_report_signature: [u8; 64], - pub qe_auth_data: QeAuthData, - pub qe_cert_data: CertData, -} - -impl QuoteSignatureDataV3 { - pub fn from_bytes(raw_bytes: &[u8]) -> QuoteSignatureDataV3 { - let mut isv_enclave_report_signature = [0u8; 64]; - let mut ecdsa_attestation_key = [0u8; 64]; - let mut qe_report_signature = [0u8; 64]; - - isv_enclave_report_signature.copy_from_slice(&raw_bytes[0..64]); - ecdsa_attestation_key.copy_from_slice(&raw_bytes[64..128]); - let qe_report = EnclaveReport::from_bytes(&raw_bytes[128..512]); - qe_report_signature.copy_from_slice(&raw_bytes[512..576]); - let qe_auth_data = QeAuthData::from_bytes(&raw_bytes[576..]); - let qe_cert_data_start = 576 + 2 + qe_auth_data.size as usize; - let qe_cert_data = CertData::from_bytes(&raw_bytes[qe_cert_data_start..]); - - QuoteSignatureDataV3 { - isv_enclave_report_signature, - ecdsa_attestation_key, - qe_report, - qe_report_signature, - qe_auth_data, - qe_cert_data, - } - } -} \ No newline at end of file diff --git a/src/types/quotes/version_4.rs b/src/types/quotes/version_4.rs deleted file mode 100644 index 5c6d716..0000000 --- a/src/types/quotes/version_4.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::{CertData, QuoteHeader, body::*}; -use crate::constants::{ENCLAVE_REPORT_LEN, SGX_TEE_TYPE, TD10_REPORT_LEN, TDX_TEE_TYPE}; - -#[derive(Clone, Debug)] -pub struct QuoteV4 { - pub header: QuoteHeader, // [48 bytes] - // Header of Quote data structure. - // This field is transparent (the user knows its internal structure). - // Rest of the Quote data structure can be treated as opaque (hidden from the user). - pub quote_body: QuoteBody, // May either contain a SGX Enclave Report (384 bytes) or TD10 Report (584 bytes) - pub signature_len: u32, // [4 bytes] - // Size of the Quote Signature Data structure in bytes. - pub signature: QuoteSignatureDataV4, // [variable bytes] -} - -impl QuoteV4 { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - let header = QuoteHeader::from_bytes(&raw_bytes[0..48]); - let quote_body; - let mut offset: usize = 48; - match header.tee_type { - SGX_TEE_TYPE => { - offset += ENCLAVE_REPORT_LEN; - quote_body = QuoteBody::SGXQuoteBody(EnclaveReport::from_bytes(&raw_bytes[48..offset])); - }, - TDX_TEE_TYPE => { - offset += TD10_REPORT_LEN; - quote_body = QuoteBody::TD10QuoteBody(TD10ReportBody::from_bytes(&raw_bytes[48..offset])); - }, - _ => { - panic!("Unknown TEE type") - } - } - let signature_len = u32::from_le_bytes([ - raw_bytes[offset], - raw_bytes[offset + 1], - raw_bytes[offset + 2], - raw_bytes[offset + 3] - ]); - offset += 4; - let signature_slice = &raw_bytes[offset..offset+signature_len as usize]; - let signature = QuoteSignatureDataV4::from_bytes(signature_slice); - - QuoteV4 { - header, - quote_body, - signature_len, - signature, - } - } -} - -#[derive(Clone, Debug)] -pub struct QuoteSignatureDataV4 { - pub quote_signature: [u8; 64], // [64 bytes] - // ECDSA signature, the r component followed by the s component, 2 x 32 bytes. - // Public part of the Attestation Key generated by the Quoting Enclave. - pub ecdsa_attestation_key: [u8; 64],// [64 bytes] - // EC KT-I Public Key, the x-coordinate followed by the y-coordinate (on the RFC 6090 P-256 curve), 2 x 32 bytes. - // Public part of the Attestation Key generated by the Quoting Enclave. - pub qe_cert_data: CertData, // [variable bytes] - // QE Cert Data -} - -impl QuoteSignatureDataV4 { - pub fn from_bytes(raw_bytes: &[u8]) -> Self { - let mut quote_signature = [0; 64]; - quote_signature.copy_from_slice(&raw_bytes[0..64]); - let mut ecdsa_attestation_key = [0; 64]; - ecdsa_attestation_key.copy_from_slice(&raw_bytes[64..128]); - let qe_cert_data = CertData::from_bytes(&raw_bytes[128..]); - - QuoteSignatureDataV4 { - quote_signature, - ecdsa_attestation_key, - qe_cert_data, - } - } -} diff --git a/src/types/report.rs b/src/types/report.rs new file mode 100644 index 0000000..7932db8 --- /dev/null +++ b/src/types/report.rs @@ -0,0 +1,169 @@ +use zerocopy::little_endian; + +// sgx_report.h in linux-sgx repository by Intel. +const SGX_CPUSVN_SIZE: usize = 16; +const SGX_HASH_SIZE: usize = 32; + +/// EnclaveReportBody is the body of the SGX report. +/// +#[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] +#[repr(C)] +pub struct EnclaveReportBody { + // (0) CPU Security Version + // uint8_t cpusvn[SGX_CPUSVN_SIZE]; + cpusvn: [u8; SGX_CPUSVN_SIZE], + + // (16) Selector for which fields are defined in SSA.MISC + // uint32_t misc_select; + pub misc_select: little_endian::U32, + + // (20) Reserved1 for future use + // uint8_t reserved1[12]; + _reserved_1: [u8; 12], + + // (32) Enclave extended product ID + // uint8_t isvextprodid[16]; + _isv_ext_prod_id: [u8; 16], + + // (48) Enclave attributes + // sgx_attributes_t attributes; + pub sgx_attributes: [u8; 16], + + // (64) Enclave measurement + // uint8_t mrenclave[SGX_HASH_SIZE]; + pub mr_enclave: [u8; SGX_HASH_SIZE], + + // (96) Reserved2 for future use + // uint8_t reserved2[32]; + _reserved_2: [u8; 32], + + // (128) The value of the enclave's SIGNER measurement + // uint8_t mrsigner[SGX_HASH_SIZE]; + pub mr_signer: [u8; SGX_HASH_SIZE], + + // (160) Reserved3 for future use + // uint8_t reserved3[32]; + _reserved_3: [u8; 32], + + // (192) Enclave Configuration Security Version + // uint8_t configid[64]; + _config_id: [u8; 64], + + // (256) Enclave product ID + // uint16_t isvprodid; + pub isv_prod_id: little_endian::U16, + + // (258) Enclave security version + // uint16_t isvsvn; + pub isv_svn: little_endian::U16, + + // (260) Enclave configuration security version + // uint16_t configsvn; + _config_svn: little_endian::U16, + + // (262) Reserved4 for future use + // uint8_t reserved4[42]; + _reserved_4: [u8; 42], + + // (304) Enclave family ID + // uint8_t isv_family_id[16]; + _isv_family_id: [u8; 16], + + // (320) User Report data + // sgx_report_data_t report_data; + pub user_report_data: [u8; 64], + // Total 384 bytes +} + +impl TryFrom<[u8; std::mem::size_of::()]> for EnclaveReportBody { + type Error = anyhow::Error; + + fn try_from( + value: [u8; std::mem::size_of::()], + ) -> Result { + let report = ::read_from(&value) + .expect("failed to read enclave report body"); + + Ok(report) + } +} + +/// Td10ReportBody is the body of the TDX 1.0 Quotes +#[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] +#[repr(C)] +pub struct Td10ReportBody { + // (0) Describes the TCB of TDX. + // uint8_t tee_tcb_svn[16]; + pub tee_tcb_svn: [u8; 16], + + // (16) Measurement of the TDX Module. + // uint8_t mrseam[48]; + pub mr_seam: [u8; 48], + + // (64) Measurement of the TDX Module Signer. + // uint8_t mrsignerseam[48]; + pub mr_signer_seam: [u8; 48], + + // (112) TDX Attributes. + // uint8_t seam_attributes[8]; + pub seam_attributes: [u8; 8], + + // (120) TD Attributes. + // uint8_t td_attributes[8]; + pub td_attributes: [u8; 8], + + // (128) XFAM (eXtended Features Available Mask) is defined as a 64b bitmap, + // which has the same format as XCR0 or IA32_XSS MSR. + // uint8_t xfam[8§]; + pub xfam: [u8; 8], + + // (136) Measurement of the initial contents of the TD. + // uint8_t mrtd[48]; + pub mr_td: [u8; 48], + + // (184) Software-defined ID for non-owner-defined configuration of the TD, + // e.g., runtime or OS configuration. + // uint8_t mrconfigid[48]; + pub mr_config_id: [u8; 48], + + // (232) Software-defined ID for the TD’s owner. + // uint8_t mrowner[48]; + pub mr_owner: [u8; 48], + + // (280) Software-defined ID for owner-defined configuration of the TD, + // e.g., specific to the workload rather than the runtime or OS. + // uint8_t mrownerconfig[48]; + pub mr_owner_config: [u8; 48], + + // (328) Measurement of the Root of Trust for Measurement (RTM) for the TD. + // uint8_t rtmr0[48]; + pub rtm_r0: [u8; 48], + + // (376) Measurement of the Root of Trust for Measurement (RTM) for the TD. + // uint8_t rtmr1[48]; + pub rtm_r1: [u8; 48], + + // (424) Measurement of the Root of Trust for Measurement (RTM) for the TD. + // uint8_t rtmr2[48]; + pub rtm_r2: [u8; 48], + + // (472) Measurement of the Root of Trust for Measurement (RTM) for the TD. + // uint8_t rtmr3[48]; + pub rtm_r3: [u8; 48], + + // (520) User Report Data. + // sgx_report_data_t report_data; + pub user_report_data: [u8; 64], + // Total 584 bytes +} + +impl TryFrom<[u8; std::mem::size_of::()]> for Td10ReportBody { + type Error = anyhow::Error; + + fn try_from(value: [u8; std::mem::size_of::()]) -> Result { + let report = ::read_from(&value) + .expect("failed to read tdx report body"); + + Ok(report) + } +} diff --git a/src/types/sgx_x509.rs b/src/types/sgx_x509.rs new file mode 100644 index 0000000..f6bdea2 --- /dev/null +++ b/src/types/sgx_x509.rs @@ -0,0 +1,466 @@ +//! SGX X.509 Certificate Extension Parser +//! +//! This module provides functionality to parse Intel SGX-specific extensions +//! from X.509 certificates, particularly PCK (Platform Certificate Key) certificates. +//! +//! # Overview +//! Intel SGX attestation relies on certificates that contain custom extensions +//! with information about platform security versions, identity, and configuration. +//! This module defines structures and parsing logic for these SGX-specific extensions. +//! +//! # Main Components +//! - `SgxPckExtension`: Main structure containing parsed SGX extension data +//! - OID constants: Defines Object Identifiers for SGX extensions +//! - Parsing logic: Functions to extract and validate extension data +//! - Type conversion: Traits to convert ASN.1 encoded values to Rust types + +use std::collections::HashMap; + +use anyhow::{Context, Error, Result, anyhow}; +use asn1::{ObjectIdentifier, SequenceOf, oid}; + +/// Intel SGX Extensions OID root +/// Identifies the root OID for all SGX extensions (1.2.840.113741.1.13.1) +pub const SGX_EXTENSIONS_OID: &str = "1.2.840.113741.1.13.1"; +const _SGX_EXTENSIONS_OID_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1); + +/// Platform Provisioning ID (PPID) OID +/// Uniquely identifies an SGX-enabled platform +const PPID_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 1); + +/// TCB (Trusted Computing Base) Information OID +/// Contains security version numbers for platform components +const TCB_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2); + +/// TCB Component SVN (Security Version Numbers) OIDs +/// Each component has its own security version number +const TCB_COMP01SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 1); +const TCB_COMP02SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 2); +const TCB_COMP03SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 3); +const TCB_COMP04SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 4); +const TCB_COMP05SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 5); +const TCB_COMP06SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 6); +const TCB_COMP07SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 7); +const TCB_COMP08SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 8); +const TCB_COMP09SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 9); +const TCB_COMP10SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 10); +const TCB_COMP11SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 11); +const TCB_COMP12SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 12); +const TCB_COMP13SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 13); +const TCB_COMP14SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 14); +const TCB_COMP15SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 15); +const TCB_COMP16SVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 16); +const TCB_PCESVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 17); +const TCB_CPUSVN_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 2, 18); + +/// PCE ID (Platform Certificate Enclave ID) OID +/// Identifies the specific PCE instance +const PCE_ID_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 3); + +/// FMSPC (Family-Model-Stepping-Platform-CustomSKU) OID +/// Platform identifier used for TCB tracking +const FMSPC_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 4); + +/// SGX Type OID +/// Indicates whether platform is standard or scalable +const SGX_TYPE_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 5); + +/// Platform Instance ID OID +/// Unique identifier for the specific platform instance +const PLATFORM_INSTANCE_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 6); + +/// Configuration OID +/// Contains platform configuration information +const CONFIGURATION_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 7); +const CONFIGURATION_DYNAMIC_PLATFORM_OID: ObjectIdentifier = + oid!(1, 2, 840, 113741, 1, 13, 1, 7, 1); +const CONFIGURATION_CACHED_KEYS_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 7, 2); +const CONFIGURATION_SMT_ENABLED_OID: ObjectIdentifier = oid!(1, 2, 840, 113741, 1, 13, 1, 7, 3); + +/// Field size constants for binary data +const PPID_LEN: usize = 16; +const CPUSVN_LEN: usize = 16; +const PCEID_LEN: usize = 2; +const FMSPC_LEN: usize = 6; +const PLATFORM_INSTANCE_ID_LEN: usize = 16; +const COMPSVN_LEN: usize = 16; + +/// Main structure containing parsed SGX PCK extension data +/// +/// This structure stores all the SGX-specific information extracted from +/// a PCK certificate's extensions, including platform identity, TCB levels, +/// and configuration information. +#[derive(Debug, Clone)] +pub struct SgxPckExtension { + pub ppid: [u8; PPID_LEN], + + /// TCB Information - Contains security version numbers for platform components + pub tcb: Tcb, + + /// PCE ID - Identifies the Platform Certificate Enclave instance + pub pceid: [u8; PCEID_LEN], + + /// FMSPC - Family-Model-Stepping-Platform-CustomSKU identifier + pub fmspc: [u8; FMSPC_LEN], + _sgx_type: SgxType, + _platform_instance_id: Option<[u8; PLATFORM_INSTANCE_ID_LEN]>, + _configuration: Option, +} + +impl SgxPckExtension { + pub fn is_pck_ext(oid: String) -> bool { + oid == SGX_EXTENSIONS_OID + } + + pub fn from_der(der: &[u8]) -> Result { + let mut ppid = None; + let mut tcb = None; + let mut pceid = None; + let mut fmspc = None; + let mut sgx_type = None; + let mut platform_instance_id: Option<[u8; PLATFORM_INSTANCE_ID_LEN]> = None; + let mut configuration: Option = None; + + let extensions = asn1::parse_single::>(der) + .map_err(|_| anyhow!("malformed PCK certificate"))?; + + parse_extensions( + extensions, + HashMap::from([ + ( + PPID_OID, + &mut ppid as &mut dyn OptionOfTryFromExtensionValue, + ), + (TCB_OID, &mut tcb), + (PCE_ID_OID, &mut pceid), + (FMSPC_OID, &mut fmspc), + (SGX_TYPE_OID, &mut sgx_type), + (PLATFORM_INSTANCE_OID, &mut platform_instance_id), + (CONFIGURATION_OID, &mut configuration), + ]), + )?; + + Ok(Self { + ppid: ppid.unwrap(), + tcb: tcb.unwrap(), + pceid: pceid.unwrap(), + fmspc: fmspc.unwrap(), + _sgx_type: sgx_type.unwrap(), + _platform_instance_id: platform_instance_id, + _configuration: configuration, + }) + } +} + +#[derive(asn1::Asn1Read, Debug)] +struct SgxExtension<'a> { + pub sgx_extension_id: ObjectIdentifier, + pub value: ExtensionValue<'a>, +} + +#[derive(asn1::Asn1Read)] +enum ExtensionValue<'a> { + OctetString(&'a [u8]), + Sequence(SequenceOf<'a, SgxExtension<'a>>), + Integer(u64), + Enumerated(asn1::Enumerated), + Bool(bool), +} + +impl std::fmt::Debug for ExtensionValue<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExtensionValue::OctetString(s) => write!(f, "octet string: {:?}", s), + ExtensionValue::Sequence(_) => write!(f, "sequence"), + ExtensionValue::Integer(i) => write!(f, "integer: {:?}", i), + ExtensionValue::Enumerated(e) => write!(f, "enumerated: {:?}", e), + ExtensionValue::Bool(b) => write!(f, "bool: {:?}", b), + } + } +} + +impl<'a> TryFrom> for u8 { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Integer(i) = value { + i.try_into() + .map_err(|_| anyhow!("malformed extension value in PCK certificate")) + } else { + Err(anyhow!("expected integer value")) + } + } +} + +impl<'a> TryFrom> for u16 { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Integer(i) = value { + i.try_into() + .map_err(|_| anyhow!("malformed extension value in PCK certificate")) + } else { + Err(anyhow!("expected integer value")) + } + } +} + +impl<'a, const LEN: usize> TryFrom> for [u8; LEN] { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::OctetString(s) = value { + s.try_into() + .map_err(|_| anyhow!("malformed extension value in PCK certificate")) + } else { + Err(anyhow!("expected octet string value")) + } + } +} + +impl<'a> TryFrom> for bool { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Bool(b) = value { + Ok(b) + } else { + Err(anyhow!("expected boolean value")) + } + } +} + +#[derive(Debug, Clone)] +pub struct Tcb { + pub compsvn: [u8; COMPSVN_LEN], + pub pcesvn: u16, + pub cpusvn: [u8; CPUSVN_LEN], +} + +impl<'a> TryFrom> for Tcb { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Sequence(seq) = value { + Self::try_from(seq) + } else { + Err(anyhow!("malformed extension value in PCK certificate")) + } + } +} + +impl<'a> TryFrom>> for Tcb { + type Error = Error; + + fn try_from(value: SequenceOf<'a, SgxExtension<'a>>) -> Result { + let mut compsvn = [None; COMPSVN_LEN]; + let mut pcesvn = None; + let mut cpusvn = None; + + let [ + compsvn01, + compsvn02, + compsvn03, + compsvn04, + compsvn05, + compsvn06, + compsvn07, + compsvn08, + compsvn09, + compsvn10, + compsvn11, + compsvn12, + compsvn13, + compsvn14, + compsvn15, + compsvn16, + ] = &mut compsvn; + + parse_extensions( + value, + HashMap::from([ + ( + TCB_COMP01SVN_OID, + compsvn01 as &mut dyn OptionOfTryFromExtensionValue, + ), + (TCB_COMP02SVN_OID, compsvn02), + (TCB_COMP03SVN_OID, compsvn03), + (TCB_COMP04SVN_OID, compsvn04), + (TCB_COMP05SVN_OID, compsvn05), + (TCB_COMP06SVN_OID, compsvn06), + (TCB_COMP07SVN_OID, compsvn07), + (TCB_COMP08SVN_OID, compsvn08), + (TCB_COMP09SVN_OID, compsvn09), + (TCB_COMP10SVN_OID, compsvn10), + (TCB_COMP11SVN_OID, compsvn11), + (TCB_COMP12SVN_OID, compsvn12), + (TCB_COMP13SVN_OID, compsvn13), + (TCB_COMP14SVN_OID, compsvn14), + (TCB_COMP15SVN_OID, compsvn15), + (TCB_COMP16SVN_OID, compsvn16), + (TCB_PCESVN_OID, &mut pcesvn), + (TCB_CPUSVN_OID, &mut cpusvn), + ]), + )?; + + Ok(Self { + compsvn: compsvn.map(Option::unwrap), + pcesvn: pcesvn.unwrap(), + cpusvn: cpusvn.unwrap(), + }) + } +} + +// This trait exists to allow storing different Option types in the same HashMap +// (where each T has its own TryFrom implementation). +// It solves the problem of heterogeneous types in the same HashMap. +// TODO(udit): Need to find a better way to do this +trait OptionOfTryFromExtensionValue { + // Convert ExtensionValue to the appropriate type and store it + fn parse_and_save(&mut self, value: ExtensionValue<'_>) -> Result<()>; + // Check if value is missing + fn is_none(&self) -> bool; +} + +impl OptionOfTryFromExtensionValue for Option +where + T: for<'a> TryFrom, Error = Error>, +{ + fn parse_and_save(&mut self, value: ExtensionValue<'_>) -> Result<()> { + if self.is_some() { + return Err(anyhow!("duplicate extension in PCK certificate")); + } + *self = Some(T::try_from(value)?); + Ok(()) + } + + fn is_none(&self) -> bool { + self.is_none() + } +} + +fn parse_extensions<'a>( + extensions: asn1::SequenceOf<'a, SgxExtension<'a>>, + mut attributes: HashMap, +) -> Result<()> { + for extension in extensions { + let SgxExtension { + sgx_extension_id, + value, + } = extension; + + if let Some(attr) = attributes.get_mut(&sgx_extension_id) { + attr.parse_and_save(value) + .with_context(|| sgx_extension_id.to_string())?; + } else { + return Err(anyhow!( + "unknown extension in PCK certificate: {:?}", + sgx_extension_id + )); + } + } + + for (oid, attr) in attributes { + // It seems that the platform instance id and configuration are optional in the + // PCK certificate. TODO(udit): Confirm this. For time, being this is hardcoded, + // to avoid panics, we ignore these two extensions. + if attr.is_none() && oid != PLATFORM_INSTANCE_OID && oid != CONFIGURATION_OID { + return Err(anyhow!("missing extension in PCK certificate: {:?}", oid)); + } + } + + Ok(()) +} + +#[derive(Debug, Clone)] +pub(crate) enum SgxType { + Standard, + Scalable, +} + +impl<'a> TryFrom> for SgxType { + type Error = Error; + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Enumerated(v) = value { + Self::try_from(v) + } else { + Err(anyhow!("malformed extension value in PCK certificate")) + } + } +} + +impl TryFrom for SgxType { + type Error = Error; + fn try_from(value: asn1::Enumerated) -> Result { + match value.value() { + 0 => Ok(SgxType::Standard), + 1 => Ok(SgxType::Scalable), + _ => Err(anyhow!("unknown SGX type in PCK certificate")), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Configuration { + // TODO should we let clients specify configuration requirements? + // e.g. disallow `smt_enabled = true` + _dynamic_platform: bool, + _cached_keys: bool, + _smt_enabled: bool, +} + +impl<'a> TryFrom> for Configuration { + type Error = Error; + + fn try_from(value: ExtensionValue<'a>) -> Result { + if let ExtensionValue::Sequence(v) = value { + Self::try_from(v) + } else { + Err(anyhow!("malformed extension value in PCK certificate")) + } + } +} + +impl<'a> TryFrom>> for Configuration { + type Error = Error; + + fn try_from(value: SequenceOf<'a, SgxExtension<'a>>) -> Result { + let mut dynamic_platform = None; + let mut cached_keys = None; + let mut smt_enabled = None; + + parse_extensions( + value, + HashMap::from([ + ( + CONFIGURATION_DYNAMIC_PLATFORM_OID, + &mut dynamic_platform as &mut dyn OptionOfTryFromExtensionValue, + ), + (CONFIGURATION_CACHED_KEYS_OID, &mut cached_keys), + (CONFIGURATION_SMT_ENABLED_OID, &mut smt_enabled), + ]), + )?; + + Ok(Self { + _dynamic_platform: dynamic_platform.unwrap(), + _cached_keys: cached_keys.unwrap(), + _smt_enabled: smt_enabled.unwrap(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_deserialization() { + const DATA: &[u8] = include_bytes!("../../data/sgx_x509_extension.der"); + + let ext = SgxPckExtension::from_der(DATA).unwrap(); + + assert_eq!(ext.pceid, [0u8, 0u8]); + assert_eq!(ext.tcb.pcesvn, 11); + assert_eq!(ext.tcb.compsvn[0], 4); + } +} diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs new file mode 100644 index 0000000..23bf0e9 --- /dev/null +++ b/src/types/tcb_info.rs @@ -0,0 +1,585 @@ +use std::time::SystemTime; + +use anyhow::{Context, bail}; +use chrono::{DateTime, Utc}; +use p256::ecdsa::VerifyingKey; +use p256::ecdsa::signature::Verifier; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; +use borsh::{BorshDeserialize, BorshSerialize}; +use crate::utils::borsh_datetime_as_instant; + +use super::{quote::{Quote, QuoteBody}, report::Td10ReportBody, sgx_x509::SgxPckExtension}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TcbInfoAndSignature { + #[serde(rename = "tcbInfo")] + tcb_info_raw: Box, + #[serde(with = "hex")] + signature: Vec, +} + +impl TryFrom for TcbInfoAndSignature { + type Error = serde_json::Error; + fn try_from(value: String) -> Result { + serde_json::from_str(&value) + } +} + +impl TcbInfoAndSignature { + pub fn as_tcb_info_and_verify( + &self, + current_time: SystemTime, + public_key: VerifyingKey, + ) -> anyhow::Result { + let tcb_info: TcbInfo = + serde_json::from_str(self.tcb_info_raw.get()).context("tcb info")?; + + // Make sure current time is between issue_date and next_update + let current_time: DateTime = current_time.into(); + if current_time < tcb_info.issue_date || current_time > tcb_info.next_update { + bail!("tcb info is not valid at current time"); + } + + let sig = p256::ecdsa::Signature::from_slice(&self.signature).unwrap(); + public_key + .verify(self.tcb_info_raw.get().as_bytes(), &sig) + .expect("valid signature expected"); + + if tcb_info + .tcb_levels + .iter() + .any(|e| e.tcb.version() != tcb_info.version) + { + bail!( + "mismatched tcb info versions, should all be {:?}", + tcb_info.version, + ); + } + + // tcb_type determines how to compare tcb level + // currently, only 0 is valid + if tcb_info.tcb_type != 0 { + bail!("unsupported tcb type {}", tcb_info.tcb_type,); + } + Ok(tcb_info) + } + + pub fn get_tcb_info(&self) -> anyhow::Result { + serde_json::from_slice(self.tcb_info_raw.get().as_bytes()) + .map_err(|e| anyhow::anyhow!("tcb info parsing failed: {}", e)) + } +} + +/// Version of the TcbInfo JSON structure +/// +/// In the PCS V3 API the TcbInfo version is V2, in the PCS V4 API the TcbInfo +/// version is V3. The V3 API includes advisoryIDs and changes the format of +/// the TcbLevel + +#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] +#[serde(try_from = "u16")] +#[borsh(use_discriminant = true)] +pub(crate) enum TcbInfoVersion { + V2 = 2, + V3 = 3, +} + +impl TryFrom for TcbInfoVersion { + type Error = &'static str; + fn try_from(value: u16) -> std::result::Result { + match value { + 2 => Ok(TcbInfoVersion::V2), + 3 => Ok(TcbInfoVersion::V3), + _ => Err("Unsupported TCB Info version"), + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TcbInfo { + #[serde(skip_serializing_if = "Option::is_none", rename = "id")] + id: Option, + version: TcbInfoVersion, + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub issue_date: chrono::DateTime, + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub next_update: chrono::DateTime, + #[serde(with = "hex")] + pub fmspc: [u8; 6], + #[serde(with = "hex")] + pub pce_id: [u8; 2], + tcb_type: u16, + _tcb_evaluation_data_number: u16, + #[serde(skip_serializing_if = "Option::is_none")] + tdx_module: Option, + #[serde(skip_serializing_if = "Option::is_none")] + tdx_module_identities: Option>, + tcb_levels: Vec, +} + +impl TcbInfo { + pub fn verify_tdx_module(&self, quote_body: &Td10ReportBody) -> anyhow::Result { + if self.tdx_module.is_none() { + return Err(anyhow::anyhow!("no tdx module found in tcb info")); + } + + let (tdx_module_isv_svn, tdx_module_version) = + (quote_body.tee_tcb_svn[0], quote_body.tee_tcb_svn[1]); + let tdx_module_identity_id = format!("TDX_{:02x}", tdx_module_version); + + if self.tdx_module_identities.is_none() { + return Err(anyhow::anyhow!( + "no tdx module identities found in tcb info" + )); + } + + let tdx_module_identity = self + .tdx_module_identities + .as_ref() + .unwrap() + .iter() + .find(|identity| identity.id == tdx_module_identity_id) + .ok_or(anyhow::anyhow!("tdx module identity not found in tcb info"))?; + + // Get the TDX module reference based on version + let (mrsigner, attributes) = if tdx_module_version > 0 { + ( + &tdx_module_identity.mrsigner, + &tdx_module_identity.attributes, + ) + } else { + let tdx_module = self.tdx_module.as_ref().unwrap(); + (&tdx_module.mrsigner, &tdx_module.attributes) + }; + + // Check for mismatches with a single validation + if mrsigner != "e_body.mr_signer_seam { + return Err(anyhow::anyhow!( + "mrsigner mismatch between tdx module identity and tdx quote body" + )); + } + + if attributes != "e_body.seam_attributes { + return Err(anyhow::anyhow!( + "attributes mismatch between tdx module identity and tdx quote body" + )); + } + + let tcb_level = tdx_module_identity + .tcb_levels + .iter() + .find(|level| level.in_tcb_level(tdx_module_isv_svn)) + .ok_or(anyhow::anyhow!( + "no tcb level found for tdx module identity within tdx module levels" + ))?; + + Ok(tcb_level.tcb_status) + } + + pub fn converge_tcb_status_with_tdx_module( + platform_status: TcbStatus, + tdx_module_status: TcbStatus, + ) -> TcbStatus { + // Only adjust if TDX module is OutOfDate + if tdx_module_status != TcbStatus::OutOfDate { + return tdx_module_status; + } + + match platform_status { + TcbStatus::UpToDate | TcbStatus::SWHardeningNeeded => TcbStatus::OutOfDate, + + TcbStatus::ConfigurationNeeded | TcbStatus::ConfigurationAndSWHardeningNeeded => { + TcbStatus::OutOfDateConfigurationNeeded + }, + + _ => platform_status, + } + } + /// Converge platform TCB status with QE TCB status + /// + /// This function implements the rules for combining platform and Quote Enclave TCB + /// status values, prioritizing the more severe status according to Intel's rules. + pub fn converge_tcb_status_with_qe_tcb( + platform_status: TcbStatus, + qe_status: TcbStatus, + ) -> TcbStatus { + // Only adjust status if QE is OutOfDate + if qe_status != TcbStatus::OutOfDate { + return platform_status; + } + + match platform_status { + // These statuses get overridden to OutOfDate + TcbStatus::UpToDate | TcbStatus::SWHardeningNeeded => TcbStatus::OutOfDate, + + // These statuses change to reflect both configuration and outdated problems + TcbStatus::ConfigurationNeeded | TcbStatus::ConfigurationAndSWHardeningNeeded => { + TcbStatus::OutOfDateConfigurationNeeded + }, + + // All other statuses remain unchanged + _ => platform_status, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TcbLevel { + pub tcb: Tcb, + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub tcb_date: chrono::DateTime, + pub tcb_status: TcbStatus, + #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] + pub advisory_ids: Option>, +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +#[borsh(use_discriminant = true)] +pub enum TcbStatus { + UpToDate, + OutOfDate, + ConfigurationNeeded, + SWHardeningNeeded, + ConfigurationAndSWHardeningNeeded, + OutOfDateConfigurationNeeded, + Revoked, + Unspecified, +} + +impl std::fmt::Display for TcbStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TcbStatus::UpToDate => write!(f, "UpToDate"), + TcbStatus::OutOfDate => write!(f, "OutOfDate"), + TcbStatus::ConfigurationNeeded => write!(f, "ConfigurationNeeded"), + TcbStatus::SWHardeningNeeded => write!(f, "SWHardeningNeeded"), + TcbStatus::ConfigurationAndSWHardeningNeeded => write!(f, "ConfigurationAndSWHardeningNeeded"), + TcbStatus::OutOfDateConfigurationNeeded => write!(f, "OutOfDateConfigurationNeeded"), + TcbStatus::Revoked => write!(f, "Revoked"), + TcbStatus::Unspecified => write!(f, "Unspecified"), + } + } +} + +/// Contains information identifying a TcbLevel. +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[serde(untagged)] +#[borsh(use_discriminant = true)] +pub enum Tcb { + V2(TcbV2), + V3(TcbV3), +} + +impl Tcb { + fn version(&self) -> TcbInfoVersion { + match self { + Tcb::V2(_) => TcbInfoVersion::V2, + Tcb::V3(_) => TcbInfoVersion::V3, + } + } +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct TcbV3 { + sgxtcbcomponents: [TcbComponentV3; 16], + #[serde(skip_serializing_if = "Option::is_none")] + tdxtcbcomponents: Option<[TcbComponentV3; 16]>, + pcesvn: u16, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, Copy, BorshSerialize, BorshDeserialize)] +pub struct TcbComponentV3 { + svn: u8, +} + + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct TcbV2 { + sgxtcbcomp01svn: u8, + sgxtcbcomp02svn: u8, + sgxtcbcomp03svn: u8, + sgxtcbcomp04svn: u8, + sgxtcbcomp05svn: u8, + sgxtcbcomp06svn: u8, + sgxtcbcomp07svn: u8, + sgxtcbcomp08svn: u8, + sgxtcbcomp09svn: u8, + sgxtcbcomp10svn: u8, + sgxtcbcomp11svn: u8, + sgxtcbcomp12svn: u8, + sgxtcbcomp13svn: u8, + sgxtcbcomp14svn: u8, + sgxtcbcomp15svn: u8, + sgxtcbcomp16svn: u8, + pcesvn: u16, +} + +impl Tcb { + pub fn pcesvn(&self) -> u16 { + match self { + Self::V2(v2) => v2.pcesvn, + Self::V3(v3) => v3.pcesvn, + } + } + + pub fn sgx_tcb_components(&self) -> [u8; 16] { + match self { + Self::V2(v2) => [ + v2.sgxtcbcomp01svn, + v2.sgxtcbcomp02svn, + v2.sgxtcbcomp03svn, + v2.sgxtcbcomp04svn, + v2.sgxtcbcomp05svn, + v2.sgxtcbcomp06svn, + v2.sgxtcbcomp07svn, + v2.sgxtcbcomp08svn, + v2.sgxtcbcomp09svn, + v2.sgxtcbcomp10svn, + v2.sgxtcbcomp11svn, + v2.sgxtcbcomp12svn, + v2.sgxtcbcomp13svn, + v2.sgxtcbcomp14svn, + v2.sgxtcbcomp15svn, + v2.sgxtcbcomp16svn, + ], + Self::V3(v3) => v3.sgxtcbcomponents.map(|comp| comp.svn), + } + } + + pub fn tdx_tcb_components(&self) -> Option<[u8; 16]> { + match self { + Self::V2(_) => None, + Self::V3(v3) => v3.tdxtcbcomponents.map(|components| components.map(|comp| comp.svn)), + } + } +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TdxModule { + #[serde(with = "hex", rename = "mrsigner")] + mrsigner: [u8; 48], + #[serde(with = "hex")] + attributes: [u8; 8], + #[serde(with = "hex")] + attributes_mask: [u8; 8], +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TdxModuleIdentity { + #[serde(rename = "id")] + id: String, + #[serde(with = "hex", rename = "mrsigner")] + mrsigner: [u8; 48], + #[serde(with = "hex")] + attributes: [u8; 8], + #[serde(with = "hex")] + attributes_mask: [u8; 8], + tcb_levels: Vec, +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TdxTcbLevel { + tcb: TcbTdx, + #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + tcb_date: chrono::DateTime, + tcb_status: TcbStatus, + #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] + advisory_ids: Option>, +} + +impl TdxTcbLevel { + pub fn in_tcb_level(&self, isv_svn: u8) -> bool { + self.tcb.isvsvn <= isv_svn + } +} + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct TcbTdx { + isvsvn: u8, +} + +impl TcbStatus { + + /// Determine the status of the TCB level that is trustable for the platform + /// + /// This function performs TCB (Trusted Computing Base) level verification by: + /// 1. Finding a matching SGX TCB level based on PCK extension values + /// 2. Extracting the SGX TCB status and advisories + /// 3. Checking for TDX TCB status if applicable + /// + /// Returns: + /// - A tuple containing (sgx_tcb_status, tdx_tcb_status, advisory_ids) + /// - sgx_tcb_status: Status of SGX platform components + /// - tdx_tcb_status: Status of TDX components (defaults to Unspecified if not applicable) + /// - advisory_ids: List of security advisories affecting this TCB level + pub fn lookup( + pck_extension: &SgxPckExtension, + tcb_info: &TcbInfo, + quote: &Quote, + ) -> anyhow::Result<(Self, Self, Vec)> { + // Find first matching TCB level with its index + let (index, first_matching_level) = tcb_info + .tcb_levels + .iter() + .enumerate() + .find(|(_, level)| TcbStatus::pck_in_tcb_level(level, pck_extension)) + .ok_or_else(|| anyhow::anyhow!("unsupported TCB in pck extension"))?; + + // Extract the SGX TCB status and advisories from the matching level + let sgx_tcb_status = first_matching_level.tcb_status; + let mut advisory_ids = first_matching_level.advisory_ids.clone().unwrap_or_default(); + + // Default TDX TCB status to Unspecified + // Will be updated if a valid TDX module is found in the quote + let mut tdx_tcb_status = TcbStatus::Unspecified; + + // Check if the quote contains a TDX module (TD 1.0 Quote Body) + if let QuoteBody::Td10QuoteBody(body) = "e.body { + // Start iterating from the found sgx matching level + for level in &tcb_info.tcb_levels[index..] { + // Process each level starting from the matching one + if let Some(tdx_tcb_components) = level.tcb.tdx_tcb_components() { + let components_match = tdx_tcb_components + .iter() + .zip(body.tee_tcb_svn.iter()) + .all(|(&comp, &svn)| comp >= svn); + + if components_match { + tdx_tcb_status = level.tcb_status; + advisory_ids = level.advisory_ids.clone().unwrap_or_default(); + break; + } + } else { + // This should not happen, meaning if you have a Td10QuoteBody, you should have a TDX TCB Component present in the TCB Info + return Err(anyhow::anyhow!("did not find tdx tcb components in tcb info when Td10QuoteBody is provided for the quote")); + } + } + } + + // Return the final status determination as a tuple + Ok((sgx_tcb_status, tdx_tcb_status, advisory_ids)) + } + + /// Returns true if all the pck componenets are >= all the tcb level components and e + /// the pck pcesvn is >= the tcb level pcesvn. + fn pck_in_tcb_level(level: &TcbLevel, pck_extension: &SgxPckExtension) -> bool { + const SVN_LENGTH: usize = 16; + let pck_components: &[u8; SVN_LENGTH] = &pck_extension.tcb.compsvn; + + pck_components + .iter() + .zip(level.tcb.sgx_tcb_components()) + .all(|(&pck, tcb)| pck >= tcb) + && pck_extension.tcb.pcesvn >= level.tcb.pcesvn() + } + +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parsing_tcb_info_without_tdx_module() { + let json = include_str!("../../data/tcb_info_v2.json"); + let tcb_info_and_signature: TcbInfoAndSignature = serde_json::from_str(json).unwrap(); + let tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); + + assert_eq!(tcb_info.tdx_module.is_none(), true); + } + + #[test] + fn test_parsing_tcb_info_with_tdx_module() { + let json = include_str!("../../data/tcb_info_v3_with_tdx_module.json"); + let tcb_info_and_signature: TcbInfoAndSignature = serde_json::from_str(json).unwrap(); + let original_tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); + assert_eq!(original_tcb_info.tdx_module.is_some(), true); + + // Serialize and Deserialize the TcbInfo + let tcb_info_borsh = borsh::to_vec(&original_tcb_info).unwrap(); + let tcb_info_deserialized: TcbInfo = borsh::from_slice(&tcb_info_borsh).unwrap(); + + // 3. Verify that deserialized matches original + assert_eq!(original_tcb_info.version, tcb_info_deserialized.version); + assert_eq!(original_tcb_info.issue_date, tcb_info_deserialized.issue_date); + assert_eq!(original_tcb_info.next_update, tcb_info_deserialized.next_update); + assert_eq!(original_tcb_info.fmspc, tcb_info_deserialized.fmspc); + assert_eq!(original_tcb_info.pce_id, tcb_info_deserialized.pce_id); + assert_eq!(original_tcb_info.tcb_type, tcb_info_deserialized.tcb_type); + + if let Some(original_tdx) = &original_tcb_info.tdx_module { + let deserialized_tdx = tcb_info_deserialized.tdx_module.as_ref().unwrap(); + assert_eq!(original_tdx.mrsigner, deserialized_tdx.mrsigner); + assert_eq!(original_tdx.attributes, deserialized_tdx.attributes); + assert_eq!(original_tdx.attributes_mask, deserialized_tdx.attributes_mask); + } + + // 5. Test TcbLevels + assert_eq!(original_tcb_info.tcb_levels.len(), tcb_info_deserialized.tcb_levels.len()); + + // Test the first TcbLevel in detail + let original_level = &original_tcb_info.tcb_levels[0]; + let deserialized_level = &tcb_info_deserialized.tcb_levels[0]; + + assert_eq!(original_level.tcb_date, deserialized_level.tcb_date); + assert_eq!(original_level.tcb_status, deserialized_level.tcb_status); + + // Test TcbLevel.tcb + match (&original_level.tcb, &deserialized_level.tcb) { + (Tcb::V2(original_v2), Tcb::V2(deserialized_v2)) => { + assert_eq!(original_v2.pcesvn, deserialized_v2.pcesvn); + assert_eq!(original_v2.sgxtcbcomp01svn, deserialized_v2.sgxtcbcomp01svn); + // Add more component checks as needed + }, + (Tcb::V3(original_v3), Tcb::V3(deserialized_v3)) => { + assert_eq!(original_v3.pcesvn, deserialized_v3.pcesvn); + assert_eq!(original_v3.sgxtcbcomponents.len(), deserialized_v3.sgxtcbcomponents.len()); + + // Check if tdxtcbcomponents exist and match + if let Some(original_tdx_comps) = &original_v3.tdxtcbcomponents { + let deserialized_tdx_comps = deserialized_v3.tdxtcbcomponents.as_ref().unwrap(); + assert_eq!(original_tdx_comps.len(), deserialized_tdx_comps.len()); + for (i, comp) in original_tdx_comps.iter().enumerate() { + assert_eq!(comp.svn, deserialized_tdx_comps[i].svn); + } + } + }, + _ => panic!("Tcb variant mismatch after deserialization"), + } + + // Test TdxModuleIdentities if present + if let Some(original_identities) = &original_tcb_info.tdx_module_identities { + let deserialized_identities = tcb_info_deserialized.tdx_module_identities.as_ref().unwrap(); + assert_eq!(original_identities.len(), deserialized_identities.len()); + + // Test the first TdxModuleIdentity + let original_identity = &original_identities[0]; + let deserialized_identity = &deserialized_identities[0]; + + assert_eq!(original_identity.id, deserialized_identity.id); + assert_eq!(original_identity.mrsigner, deserialized_identity.mrsigner); + assert_eq!(original_identity.attributes, deserialized_identity.attributes); + assert_eq!(original_identity.attributes_mask, deserialized_identity.attributes_mask); + + // Test TcbLevels in TdxModuleIdentity + assert_eq!( + original_identity.tcb_levels.len(), + deserialized_identity.tcb_levels.len() + ); + + if !original_identity.tcb_levels.is_empty() { + let original_tdx_level = &original_identity.tcb_levels[0]; + let deserialized_tdx_level = &deserialized_identity.tcb_levels[0]; + + assert_eq!(original_tdx_level.tcb.isvsvn, deserialized_tdx_level.tcb.isvsvn); + assert_eq!(original_tdx_level.tcb_date, deserialized_tdx_level.tcb_date); + assert_eq!(original_tdx_level.tcb_status, deserialized_tdx_level.tcb_status); + } + } + } +} diff --git a/src/types/tcbinfo.rs b/src/types/tcbinfo.rs deleted file mode 100644 index 7bf235a..0000000 --- a/src/types/tcbinfo.rs +++ /dev/null @@ -1,655 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub enum TcbInfo { - V2(TcbInfoV2), - V3(TcbInfoV3) -} - -// TcbInfoV2: -// type: object -// description: >- -// SGX TCB Info encoded as JSON string in case of success (200 HTTP -// status code) -// properties: -// tcbInfo: -// type: object -// properties: -// version: -// type: integer -// example: 2 -// description: Version of the structure -// issueDate: -// type: string -// format: date-time -// description: >- -// Representation of date and time the TCB information -// was created. The time shall be in UTC and the -// encoding shall be compliant to ISO 8601 standard -// (YYYY-MM-DDThh:mm:ssZ) -// nextUpdate: -// type: string -// format: date-time -// description: >- -// Representation of date and time by which next TCB -// information will be issued. The time shall be in UTC -// and the encoding shall be compliant to ISO 8601 -// standard (YYYY-MM-DDThh:mm:ssZ) -// fmspc: -// type: string -// pattern: ^[0-9a-fA-F]{12}$ -// example: '000000000000' -// description: >- -// Base 16-encoded string representation of FMSPC -// (Family-Model-Stepping-Platform-CustomSKU) -// pceId: -// type: string -// pattern: ^[0-9a-fA-F]{4}$ -// example: '0000' -// description: Base 16-encoded string representation of PCE identifier -// tcbType: -// type: integer -// example: 0 -// description: >- -// Type of TCB level composition that determines TCB -// level comparison logic -// tcbEvaluationDataNumber: -// type: integer -// example: 2 -// description: >- -// A monotonically increasing sequence number changed -// when Intel updates the content of the TCB evaluation data -// set: TCB Info, QE Idenity and QVE Identity. The tcbEvaluationDataNumber -// update is synchronized across TCB Info for all flavors of -// SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE -// Identity. This sequence number allows users to easily determine -// when a particular TCB Info/QE Idenity/QVE Identiy superseedes -// another TCB Info/QE Identity/QVE Identity (value: current -// TCB Recovery event number stored in the database). -// tcbLevels: -// type: array -// description: >- -// Sorted list of supported TCB levels for given FMSPC -// encoded as a JSON array of TCB level objects -// items: -// type: object -// properties: -// tcb: -// type: object -// properties: -// pcesvn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 65535 -// sgxtcbcomp01svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp02svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp03svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp04svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp05svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp06svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp07svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp08svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp09svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp10svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp11svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp12svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp13svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp14svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp15svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// sgxtcbcomp16svn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 255 -// tcbDate: -// type: string -// format: date-time -// description: >- -// If there are security advisories published by Intel after tcbDate -// that are for issues whose mitigations are currently enforced* by SGX attestation, -// then the value of tcbStatus for the TCB level will not be UpToDate. -// Otherwise (i.e., either no advisories after or not currently enforced), -// the value of tcbStatus for the TCB level will not be OutOfDate. -// -// The time shall be in UTC and the encoding shall -// be compliant to ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). -// tcbStatus: -// type: string -// enum: -// - UpToDate -// - SWHardeningNeeded -// - ConfigurationNeeded -// - ConfigurationAndSWHardeningNeeded -// - OutOfDate -// - OutOfDateConfigurationNeeded -// - Revoked -// description: >- -// TCB level status. One of the following values: -// -// "UpToDate" - TCB level of the SGX platform is up-to-date. -// -// "SWHardeningNeeded" - TCB level of the SGX platform -// is up-to-date but due to certain issues affecting the -// platform, additional SW Hardening in the attesting -// SGX enclaves may be needed. -// -// "ConfigurationNeeded" - TCB level of the SGX platform -// is up-to-date but additional configuration of SGX -// platform may be needed. -// -// "ConfigurationAndSWHardeningNeeded" - TCB level of the -// SGX platform is up-to-date but additional configuration -// for the platform and SW Hardening in the attesting SGX -// enclaves may be needed. -// -// "OutOfDate" - TCB level of SGX platform is outdated. -// -// "OutOfDateConfigurationNeeded" - TCB level of SGX -// platform is outdated and additional configuration -// of SGX platform may be needed. -// -// "Revoked" - TCB level of SGX platform is revoked. -// The platform is not trustworthy. -// ZL: This new field is added for v3, seems like a mistake in Intel's documentation. -// Going to keep it here for now. -// advisoryIDs: -// type: array -// description: >- -// Array of Advisory IDs referring to Intel security advisories that -// provide insight into the reason(s) for the value of tcbStatus for -// this TCB level when the value is not UpToDate. -// -// Note: The value can be different for different -// FMSPCs. -// -// This field is optional. It will be present only -// if the list of Advisory IDs is not empty. -// items: -// type: string -// signature: -// type: string -// description: >- -// Base 16-encoded string representation of signature calculated over tcbInfo -// body without whitespaces using TCB Signing Key -// i.e: -// {"version":2,"issueDate":"2019-07-30T12:00:00Z","nextUpdate":"2019-08-30T12:00:00Z",...} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV2 { - pub tcb_info: TcbInfoV2Inner, - pub signature: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV2Inner { - pub version: u32, - pub issue_date: String, - pub next_update: String, - pub fmspc: String, - pub pce_id: String, - pub tcb_type: u8, - pub tcb_evaluation_data_number: u32, - pub tcb_levels: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV2TcbLevelItem { - pub tcb: TcbInfoV2TcbLevel, - pub tcb_date: String, - pub tcb_status: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV2TcbLevel { - pub sgxtcbcomp01svn: u8, - pub sgxtcbcomp02svn: u8, - pub sgxtcbcomp03svn: u8, - pub sgxtcbcomp04svn: u8, - pub sgxtcbcomp05svn: u8, - pub sgxtcbcomp06svn: u8, - pub sgxtcbcomp07svn: u8, - pub sgxtcbcomp08svn: u8, - pub sgxtcbcomp09svn: u8, - pub sgxtcbcomp10svn: u8, - pub sgxtcbcomp11svn: u8, - pub sgxtcbcomp12svn: u8, - pub sgxtcbcomp13svn: u8, - pub sgxtcbcomp14svn: u8, - pub sgxtcbcomp15svn: u8, - pub sgxtcbcomp16svn: u8, - pub pcesvn: u16, -} -// TcbInfoV3: -// type: object -// description: >- -// SGX TCB Info encoded as JSON string in case of success (200 HTTP -// status code) -// properties: -// tcbInfo: -// type: object -// properties: -// id: -// type: string -// description: Identifier of the TCB Info issued by Intel. Supported values are SGX or TDX. -// version: -// type: integer -// example: 2 -// description: Version of the structure -// issueDate: -// type: string -// format: date-time -// description: >- -// Representation of date and time the TCB information -// was created. The time shall be in UTC and the -// encoding shall be compliant to ISO 8601 standard -// (YYYY-MM-DDThh:mm:ssZ) -// nextUpdate: -// type: string -// format: date-time -// description: >- -// Representation of date and time by which next TCB -// information will be issued. The time shall be in UTC -// and the encoding shall be compliant to ISO 8601 -// standard (YYYY-MM-DDThh:mm:ssZ) -// fmspc: -// type: string -// pattern: ^[0-9a-fA-F]{12}$ -// example: '000000000000' -// description: >- -// Base 16-encoded string representation of FMSPC -// (Family-Model-Stepping-Platform-CustomSKU) -// pceId: -// type: string -// pattern: ^[0-9a-fA-F]{4}$ -// example: '0000' -// description: Base 16-encoded string representation of PCE identifier -// tcbType: -// type: integer -// example: 0 -// description: >- -// Type of TCB level composition that determines TCB -// level comparison logic -// tcbEvaluationDataNumber: -// type: integer -// example: 2 -// description: >- -// A monotonically increasing sequence number changed -// when Intel updates the content of the TCB evaluation data -// set: TCB Info, QE Idenity and QVE Identity. The tcbEvaluationDataNumber -// update is synchronized across TCB Info for all flavors of -// SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE -// Identity. This sequence number allows users to easily determine -// when a particular TCB Info/QE Idenity/QVE Identiy superseedes -// another TCB Info/QE Identity/QVE Identity (value: current -// TCB Recovery event number stored in the database). -// tdxModule: -// type: object -// description: >- -// This field is optional. It will be present only -// in context of TDX TCB Info. -// properties: -// mrsigner: -// type: string -// pattern: ^[0-9a-fA-F]{96}$ -// example: '0000000000000000000000000000000000000000000000000000000000000000' -// description: Base 16-encoded string representation of the measurement of a TDX SEAM module's signer. -// attributes: -// type: string -// pattern: ^[0-9a-fA-F]{16}$ -// example: '0000000000000000' -// description: Hex-encoded byte array (8 bytes) representing attributes "golden" value (upon applying mask) for TDX SEAM module. -// attributesMask: -// type: string -// pattern: ^[0-9a-fA-F]{16}$ -// example: 'FFFFFFFFFFFFFFFF' -// description: Hex-encoded byte array (8 bytes) representing mask to be applied to TDX SEAM module's attributes value retrieved from the platform. -// tdxModuleIdentities: -// type: array -// description: >- -// This field is optional. It will be present only in context of TDX TCB Info when the platform supports more than one TDX SEAM Module. -// items: -// type: object -// properties: -// id: -// type: string -// description: Identifier of TDX Module -// mrsigner: -// type: string -// pattern: ^[0-9a-fA-F]{96}$ -// example: '0000000000000000000000000000000000000000000000000000000000000000' -// description: Base 16-encoded string representation of the measurement of a TDX SEAM module's signer. -// attributes: -// type: string -// pattern: ^[0-9a-fA-F]{16}$ -// example: '0000000000000000' -// description: Base 16-encoded string representation of the byte array (8 bytes) representing attributes "golden" value (upon applying mask) for TDX SEAM module. -// attributesMask: -// type: string -// pattern: ^[0-9a-fA-F]{16}$ -// example: 'FFFFFFFFFFFFFFFF' -// description: Base 16-encoded string representation of the byte array (8 bytes) representing mask to be applied to TDX SEAM module's attributes value retrieved from the platform. -// tcbLevels: -// type: array -// description: >- -// Sorted list of supported TCB levels for given TDX SEAM module encoded as a JSON array of TCB level objects. -// items: -// type: object -// properties: -// tcb: -// type: object -// properties: -// isvnsvn: -// description: TDX SEAM module's ISV SVN -// type: integer -// tcbDate: -// type: string -// format: date-time -// description: >- -// If there are security advisories published by Intel after tcbDate -// that are for issues whose mitigations are currently enforced* by SGX/TDX attestation, -// then the value of tcbStatus for the TCB level will not be UpToDate. -// Otherwise (i.e., either no advisories after or not currently enforced), -// the value of tcbStatus for the TCB level will not be OutOfDate. -// -// The time shall be in UTC and the encoding shall -// be compliant to ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). -// tcbStatus: -// type: string -// enum: -// - UpToDate -// - OutOfDate -// - Revoked -// description: >- -// TCB level status. One of the following values: -// -// "UpToDate" - TCB level of the TDX SEAM Module is up-to-date. -// -// "OutOfDate" - TCB level of TDX SEAM Module is outdated. -// -// "Revoked" - TCB level of TDX SEAM Module is revoked. -// The platform is not trustworthy. -// advisoryIDs: -// type: array -// description: >- -// Array of Advisory IDs referring to Intel security advisories that -// provide insight into the reason(s) for the value of tcbStatus for -// this TCB level when the value is not UpToDate. -// -// This field is optional. It will be present only -// if the list of Advisory IDs is not empty. -// items: -// type: string -// tcbLevels: -// type: array -// description: >- -// Sorted list of supported TCB levels for given FMSPC -// encoded as a JSON array of TCB level objects -// items: -// type: object -// properties: -// tcb: -// type: object -// properties: -// sgxtcbcomponents: -// description: >- -// Array of 16 SGX TCB Components (as in CPUSVN) encoded as a JSON array of TCB Component objects. -// items: -// properties: -// svn: -// type: "integer" -// description: SVN of TCB Component. This field is mandatory. -// category: -// type: "string" -// description: Category of TCB Component (e.g. ucode, BIOS, SW). This field is optional and will be present only for selected TCB Components. -// type: -// type: "string" -// description: Type of TCB Component (e.g. Patch@Reset, Late Patch). This field is optional and will be present only for selected TCB Components. -// pcesvn: -// type: integer -// example: 0 -// minimum: 0 -// maximum: 65535 -// tdxtcbcomponents: -// description: >- -// Array of 16 TDX TCB Components (as in TEE TCB SVN array in TD Report) encoded as a JSON array of TCB Component objects. -// -// This field is optional and only present in TDX TCB Info. -// items: -// properties: -// svn: -// type: "integer" -// description: SVN of TCB Component. This field is mandatory. -// category: -// type: "string" -// description: Category of TCB Component (e.g. ucode, BIOS, SW). This field is optional and will be present only for selected TCB Components. -// type: -// type: "string" -// description: Type of TCB Component (e.g. Patch@Reset, Late Patch). This field is optional and will be present only for selected TCB Components. -// tcbDate: -// type: string -// format: date-time -// description: >- -// If there are security advisories published by Intel after tcbDate -// that are for issues whose mitigations are currently enforced* by SGX attestation, -// then the value of tcbStatus for the TCB level will not be UpToDate. -// Otherwise (i.e., either no advisories after or not currently enforced), -// the value of tcbStatus for the TCB level will not be OutOfDate. -// -// The time shall be in UTC and the encoding shall -// be compliant to ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). -// tcbStatus: -// type: string -// enum: -// - UpToDate -// - SWHardeningNeeded -// - ConfigurationNeeded -// - ConfigurationAndSWHardeningNeeded -// - OutOfDate -// - OutOfDateConfigurationNeeded -// - Revoked -// description: >- -// TCB level status. One of the following values: -// -// "UpToDate" - TCB level of the SGX platform is up-to-date. -// -// "SWHardeningNeeded" - TCB level of the SGX platform -// is up-to-date but due to certain issues affecting the -// platform, additional SW Hardening in the attesting -// SGX enclaves may be needed. -// -// "ConfigurationNeeded" - TCB level of the SGX platform -// is up-to-date but additional configuration of SGX -// platform may be needed. -// -// "ConfigurationAndSWHardeningNeeded" - TCB level of the -// SGX platform is up-to-date but additional configuration -// for the platform and SW Hardening in the attesting SGX -// enclaves may be needed. -// -// "OutOfDate" - TCB level of SGX platform is outdated. -// -// "OutOfDateConfigurationNeeded" - TCB level of SGX -// platform is outdated and additional configuration -// of SGX platform may be needed. -// -// "Revoked" - TCB level of SGX platform is revoked. -// The platform is not trustworthy. -// advisoryIDs: -// type: array -// description: >- -// Array of Advisory IDs referring to Intel security advisories that -// provide insight into the reason(s) for the value of tcbStatus for -// this TCB level when the value is not UpToDate. -// -// Note: The value can be different for different -// FMSPCs. -// -// This field is optional. It will be present only -// if the list of Advisory IDs is not empty. -// items: -// type: string -// signature: -// type: string -// description: >- -// Base 16-encoded string representation of signature calculated over tcbInfo -// body without whitespaces using TCB Signing Key -// i.e: -// {"version":2,"issueDate":"2019-07-30T12:00:00Z","nextUpdate":"2019-08-30T12:00:00Z",...} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV3 { - pub tcb_info: TcbInfoV3Inner, - pub signature: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV3Inner { - pub id: String, - pub version: u32, - pub issue_date: String, - pub next_update: String, - pub fmspc: String, - pub pce_id: String, - pub tcb_type: u8, - pub tcb_evaluation_data_number: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub tdx_module: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub tdx_module_identities: Option>, - pub tcb_levels: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TdxModule { - pub mrsigner: String, // Base 16-encoded string representation of the measurement of a TDX SEAM module’s signer. - pub attributes: String, // Hex-encoded byte array (8 bytes) representing attributes "golden" value. - pub attributes_mask: String, // Hex-encoded byte array (8 bytes) representing mask to be applied to TDX SEAM module’s - // attributes value retrieved from the platform -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TdxModuleIdentities { - pub id: String, // Identifier of TDX Module - pub mrsigner: String, // Base 16-encoded string representation of the measurement of a TDX SEAM module’s signer. - pub attributes: String, // Base 16-encoded string representation of the byte array (8 bytes) representing attributes "golden" value. - pub attributes_mask: String, // Base 16-encoded string representation of the byte array (8 bytes) representing mask to be applied to TDX SEAM module’s - // attributes value retrieved from the platform - pub tcb_levels: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TdxModuleIdentitiesTcbLevelItem { - pub tcb: TdxModuleIdentitiesTcbLevel, - pub tcb_date: String, - pub tcb_status: String, - #[serde(rename(serialize = "advisoryIDs", deserialize = "advisoryIDs"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub advisory_ids: Option>, - -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TdxModuleIdentitiesTcbLevel { - pub isvsvn: u8, // TDX SEAM module’s ISV SVN -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV3TcbLevelItem { - pub tcb: TcbInfoV3TcbLevel, - pub tcb_date: String, - pub tcb_status: String, - #[serde(rename(serialize = "advisoryIDs", deserialize = "advisoryIDs"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub advisory_ids: Option>, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbInfoV3TcbLevel { - pub sgxtcbcomponents: Vec, - pub pcesvn: u16, - #[serde(skip_serializing_if = "Option::is_none")] - pub tdxtcbcomponents: Option>, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TcbComponent { - pub svn: u8, // SVN of TCB Component. - #[serde(skip_serializing_if = "Option::is_none")] - pub category: Option, // Category of TCB Component (e.g. BIOS, OS/VMM). - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename(serialize = "type", deserialize = "type"))] - pub type_: Option, // Type of TCB Component (e.g. SGX Late Microcode Update, TXT SINIT). -} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..5631bdc --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,307 @@ +use std::time::SystemTime; + +use x509_cert::{certificate::CertificateInner, crl::CertificateList}; + +pub mod borsh_datetime_as_instant { + use borsh::{BorshDeserialize, BorshSerialize}; + use chrono::{DateTime, TimeZone, Utc}; + + pub fn serialize( + datetime: &DateTime, + writer: &mut W, + ) -> std::io::Result<()> { + // Convert DateTime to instant (seconds since UNIX epoch) + let secs = datetime.timestamp(); + let nanos = datetime.timestamp_subsec_nanos(); + + // Serialize as i64 (seconds) and u32 (nanos) + BorshSerialize::serialize(&secs, writer)?; + BorshSerialize::serialize(&nanos, writer)?; + Ok(()) + } + + pub fn deserialize(reader: &mut R) -> std::io::Result> { + // Deserialize seconds and nanos + let secs = i64::deserialize_reader(reader)?; + let nanos = u32::deserialize_reader(reader)?; + + // Reconstruct DateTime from seconds and nanos + Utc.timestamp_opt(secs, nanos).single() + .ok_or_else(|| std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Invalid datetime value" + )) + } +} + +// Module for serializing and deserializing UInt32LE with Borsh +pub mod borsh_uint32le { + use zerocopy::AsBytes; + + use crate::types::UInt32LE; + + pub fn serialize( + value: &UInt32LE, + writer: &mut W, + ) -> std::io::Result<()> { + // Use AsBytes to get the raw byte representation + writer.write_all(value.as_bytes()) + } + + pub fn deserialize(reader: &mut R) -> std::io::Result { + let mut bytes = [0u8; 4]; + reader.read_exact(&mut bytes)?; + Ok(UInt32LE::new(u32::from_le_bytes(bytes))) + } +} + +/// A module for serializing and deserializing certificate chains. +pub mod cert_chain { + use serde::{Deserialize, de, ser}; + use x509_cert::{certificate::CertificateInner, der::EncodePem}; + + use super::cert_chain_processor; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let s = ::deserialize(deserializer)?; + let pem = Box::new(s.as_bytes()); + cert_chain_processor::load_pem_chain_bpf_friendly(&pem).map_err(de::Error::custom) + } + + pub fn serialize(certs: &Vec, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut string = String::new(); + for cert in certs { + string.push_str( + &cert + .to_pem(p256::pkcs8::LineEnding::LF) + .map_err(ser::Error::custom)?, + ); + } + serializer.serialize_str(&string) + } +} + +/// A module for serializing and deserializing CRLs. +pub mod crl { + use std::str::FromStr; + + use pem::Pem; + use serde::{Deserialize, Deserializer, Serializer, de, ser}; + use x509_cert::crl::CertificateList; + use x509_cert::der::{Decode, Encode}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = ::deserialize(deserializer)?; + let pem = Pem::from_str(&s).map_err(de::Error::custom)?; + CertificateList::from_der(pem.contents()).map_err(de::Error::custom) + } + pub fn serialize( + value: &CertificateList, + serializer: S, + ) -> Result { + let pem = Pem::new("X509 CRL", value.to_der().map_err(ser::Error::custom)?); + serializer.serialize_str(&pem.to_string()) + } +} + +pub mod u32_hex { + use serde::Serializer; + use zerocopy::AsBytes; + + use crate::types::UInt32LE; + + pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + let value: [u8; 4] = hex::deserialize(deserializer)?; + Ok(value.into()) + } + pub fn serialize(value: &UInt32LE, serializer: S) -> Result { + hex::serialize(value.as_bytes(), serializer) + } +} + +pub mod cert_chain_processor { + use x509_cert::{ + certificate::CertificateInner, + der::{Decode, DecodePem}, + }; + + /// A minimal function that returns ONLY certificate byte ranges + /// This avoids any parsing to stay under BPF stack limits + pub fn find_certificate_ranges(pem_data: &[u8]) -> Vec<(usize, usize)> { + let mut ranges = Vec::new(); + let mut i = 0; + + while i < pem_data.len() { + // Find BEGIN marker + if let Some(begin_idx) = find_next_match(&pem_data[i..], b"-----BEGIN CERTIFICATE-----") + { + let begin_pos = i + begin_idx; + + // Find END marker + if let Some(end_rel_idx) = + find_next_match(&pem_data[begin_pos + 27..], b"-----END CERTIFICATE-----") + { + let end_pos = begin_pos + 27 + end_rel_idx + 25; + + // Store range rather than content + ranges.push((begin_pos, end_pos)); + + // Move past this certificate + i = end_pos; + } else { + // Incomplete certificate, move past the BEGIN marker + i = begin_pos + 27; + } + } else { + // No more certificates + break; + } + } + + ranges + } + + // Simple byte matcher with no allocation + fn find_next_match(data: &[u8], pattern: &[u8]) -> Option { + if pattern.len() > data.len() { + return None; + } + + 'outer: for i in 0..=(data.len() - pattern.len()) { + for (j, &p) in pattern.iter().enumerate() { + if data[i + j] != p { + continue 'outer; + } + } + return Some(i); + } + + None + } + + /// Process a single certificate at the specified range + fn parse_single_cert( + pem_data: &[u8], + range: (usize, usize), + ) -> anyhow::Result { + let (start, end) = range; + if start >= pem_data.len() || end > pem_data.len() || start >= end { + return Err(anyhow::anyhow!("Invalid certificate range")); + } + + let cert_slice = &pem_data[start..end]; + + // Try PEM format first + match CertificateInner::from_pem(cert_slice) { + Ok(cert) => return Ok(cert), + Err(_) => {}, // Try DER next + } + + // Try DER format as fallback (if this was base64 decoded already) + CertificateInner::from_der(cert_slice) + .map_err(|e| anyhow::anyhow!("Failed to parse certificate: {}", e)) + } + + /// Load certificate chain in chunks to avoid stack issues + pub fn load_pem_chain_bpf_friendly(pem_data: &[u8]) -> anyhow::Result> { + // Find all certificate ranges without parsing + let ranges = find_certificate_ranges(pem_data); + if ranges.is_empty() { + return Err(anyhow::anyhow!("No certificates found")); + } + + // Process each certificate individually + let mut certificates = Vec::with_capacity(ranges.len()); + for range in ranges { + // Each certificate is processed in isolation to minimize stack usage + let cert = parse_single_cert(pem_data, range)?; + certificates.push(cert); + } + + Ok(certificates) + } + + /// Load first certificate from pem data + pub fn load_first_cert_from_pem_data(pem_data: &[u8]) -> anyhow::Result { + let ranges = find_certificate_ranges(pem_data); + if ranges.is_empty() { + return Err(anyhow::anyhow!("No certificates found")); + } + parse_single_cert(pem_data, ranges[0]) + } + +} + +pub trait Expireable { + fn valid_at(&self, timestamp: SystemTime) -> bool; +} + +impl Expireable for CertificateList { + /// Validate CRL creation/expiration + fn valid_at(&self, timestamp: SystemTime) -> bool { + if let Some(na) = self.tbs_cert_list.next_update.map(|t| t.to_system_time()) { + if na <= timestamp { + return false; + } + } + + // return false if the crl is for the future + let nb = self.tbs_cert_list.this_update.to_system_time(); + if nb >= timestamp { + return false; + } + + true + } +} + +impl Expireable for CertificateInner { + /// Validate a single certificate not_before/not_after + fn valid_at(&self, timestamp: SystemTime) -> bool { + let nb = self.tbs_certificate.validity.not_before.to_system_time(); + let na = self.tbs_certificate.validity.not_after.to_system_time(); + !(timestamp <= nb || na <= timestamp) + } +} + +impl Expireable for &[CertificateInner] { + fn valid_at(&self, timestamp: SystemTime) -> bool { + self.iter().all(|cert| cert.valid_at(timestamp)) + } +} + +impl Expireable for Vec { + fn valid_at(&self, timestamp: SystemTime) -> bool { + self.as_slice().valid_at(timestamp) + } +} + +/// Removes `std::mem::size_of()` bytes from the front of `bytes` and returns it as a `T`. +/// +/// Returns `None` and leaves `bytes` unchanged if it isn't long enough. +pub fn read_from_bytes(bytes: &mut &[u8]) -> Option { + let front = T::read_from_prefix(bytes)?; + *bytes = &bytes[std::mem::size_of::()..]; + Some(front) +} + +/// Removes a slice of `size` from the front of `bytes` and returns it +/// +/// Note: Caller must ensure that the slice is large enough +pub fn read_bytes<'a>(bytes: &mut &'a [u8], size: usize) -> &'a [u8] { + let (front, rest) = bytes.split_at(size); + *bytes = rest; + front +} diff --git a/src/utils/cert.rs b/src/utils/cert.rs deleted file mode 100644 index 5ce1897..0000000 --- a/src/utils/cert.rs +++ /dev/null @@ -1,624 +0,0 @@ -use x509_parser::oid_registry::asn1_rs::{ - oid, Boolean, Enumerated, FromDer, Integer, OctetString, Oid, Sequence, -}; -use x509_parser::oid_registry::OID_X509_EXT_CRL_DISTRIBUTION_POINTS; -use x509_parser::prelude::*; - -use crate::constants::{SGX_TEE_TYPE, TDX_TEE_TYPE}; -use crate::types::cert::{PckPlatformConfiguration, SgxExtensionTcbLevel, SgxExtensions}; -use crate::types::tcbinfo::{TcbComponent, TcbInfoV2, TcbInfoV3}; -use crate::types::TcbStatus; -use crate::utils::crypto::verify_p256_signature_der; -use crate::utils::hash::{keccak256sum, sha256sum}; - -pub fn hash_x509_keccak256(cert: &X509Certificate) -> [u8; 32] { - keccak256sum(cert.tbs_certificate.as_ref()) -} - -pub fn hash_x509_sha256(cert: &X509Certificate) -> [u8; 32] { - sha256sum(cert.tbs_certificate.as_ref()) -} - -pub fn hash_crl_keccak256(cert: &CertificateRevocationList) -> [u8; 32] { - keccak256sum(cert.tbs_cert_list.as_ref()) -} - -pub fn hash_crl_sha256(cert: &CertificateRevocationList) -> [u8; 32] { - sha256sum(cert.tbs_cert_list.as_ref()) -} - -pub fn pem_to_der(pem_bytes: &[u8]) -> Vec { - // convert from raw pem bytes to pem objects - let pems = parse_pem(pem_bytes).unwrap(); - // convert from pem objects to der bytes - // to make it more optimize, we'll read get all the lengths of the der bytes - // and then allocate the buffer once - let der_bytes_len: usize = pems.iter().map(|pem| pem.contents.len()).sum(); - let mut der_bytes = Vec::with_capacity(der_bytes_len); - for pem in pems { - der_bytes.extend_from_slice(&pem.contents); - } - der_bytes -} - -pub fn parse_pem(raw_bytes: &[u8]) -> Result, PEMError> { - Pem::iter_from_buffer(raw_bytes).collect() -} - -pub fn parse_crl_der<'a>(raw_bytes: &'a [u8]) -> CertificateRevocationList<'a> { - let (_, crl) = CertificateRevocationList::from_der(raw_bytes).unwrap(); - crl -} - -pub fn parse_x509_der<'a>(raw_bytes: &'a [u8]) -> X509Certificate<'a> { - let (_, cert) = X509Certificate::from_der(raw_bytes).unwrap(); - cert -} - -pub fn parse_x509_der_multi<'a>(raw_bytes: &'a [u8]) -> Vec> { - let mut certs = Vec::new(); - let mut i = raw_bytes; - while i.len() > 0 { - let (j, cert) = X509Certificate::from_der(i).unwrap(); - certs.push(cert); - i = j; - } - certs -} - -pub fn parse_certchain<'a>(pem_certs: &'a [Pem]) -> Vec> { - pem_certs - .iter() - .map(|pem| pem.parse_x509().unwrap()) - .collect() -} - -pub fn verify_certificate(cert: &X509Certificate, signer_cert: &X509Certificate, current_time: u64) -> bool { - // verifies that the certificate is unexpired - let issue_date = cert.validity().not_before.timestamp() as u64; - let expiry_date = cert.validity().not_after.timestamp() as u64; - if (current_time < issue_date) || (current_time > expiry_date) { - return false; - } - - // verifies that the certificate is valid - let data = cert.tbs_certificate.as_ref(); - let signature = cert.signature_value.as_ref(); - let public_key = signer_cert.public_key().subject_public_key.as_ref(); - - // make sure that the issuer is the signer - if cert.issuer() != signer_cert.subject() { - return false; - } - - verify_p256_signature_der(data, signature, public_key) -} - -pub fn verify_crl(crl: &CertificateRevocationList, signer_cert: &X509Certificate, current_time: u64) -> bool { - // verifies that the crl is unexpired - let issue_date = crl.last_update().timestamp() as u64; - let expiry_date = if let Some(next_update) = crl.next_update() { - next_update.timestamp() as u64 - } else { - // next update field is optional - u64::max_value() - }; - - if (current_time < issue_date) || (current_time > expiry_date) { - return false; - } - - // verifies that the crl is valid - let data = crl.tbs_cert_list.as_ref(); - let signature = crl.signature_value.as_ref(); - let public_key = signer_cert.public_key().subject_public_key.as_ref(); - // make sure that the issuer is the signer - if crl.issuer() != signer_cert.subject() { - return false; - } - verify_p256_signature_der(data, signature, public_key) -} - -// we'll just verify that the certchain signature matches, any other checks will be done by the caller -pub fn verify_certchain_signature<'a, 'b>( - certs: &[X509Certificate<'a>], - root_cert: &X509Certificate<'b>, - current_time: u64 -) -> bool { - // verify that the cert chain is valid - let mut iter = certs.iter(); - let mut prev_cert = iter.next().unwrap(); - for cert in iter { - // verify that the previous cert signed the current cert - if !verify_certificate(prev_cert, cert, current_time) { - return false; - } - prev_cert = cert; - } - // verify that the root cert signed the last cert - verify_certificate(prev_cert, root_cert, current_time) -} - -pub fn is_cert_revoked<'a, 'b>( - cert: &X509Certificate<'a>, - crl: &CertificateRevocationList<'b>, -) -> bool { - crl.iter_revoked_certificates() - .any(|entry| entry.user_certificate == cert.tbs_certificate.serial) -} - -pub fn get_x509_subject_cn(cert: &X509Certificate) -> String { - let subject = cert.subject(); - let cn = subject.iter_common_name().next().unwrap(); - cn.as_str().unwrap().to_string() -} - -pub fn get_x509_issuer_cn(cert: &X509Certificate) -> String { - let issuer = cert.issuer(); - let cn = issuer.iter_common_name().next().unwrap(); - cn.as_str().unwrap().to_string() -} - -pub fn get_crl_uri(cert: &X509Certificate) -> Option { - let crl_ext = cert - .get_extension_unique(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) - .unwrap() - .unwrap(); - let crl_uri = match crl_ext.parsed_extension() { - ParsedExtension::CRLDistributionPoints(crls) => { - match &crls.iter().next().unwrap().distribution_point { - Some(DistributionPointName::FullName(uri)) => { - let uri = &uri[0]; - match uri { - GeneralName::URI(uri) => Some(uri.to_string()), - _ => None, - } - } - _ => None, - } - } - _ => { - unreachable!(); - } - }; - crl_uri -} - -pub fn get_asn1_bool<'a>(bytes: &'a [u8], oid_str: &str) -> (&'a [u8], bool) { - let (k, asn1_seq) = Sequence::from_der(bytes).unwrap(); - let (l, asn1_oid) = Oid::from_der(asn1_seq.content.as_ref()).unwrap(); - assert!(oid_str.eq(&asn1_oid.to_id_string())); - let (l, asn1_bool) = Boolean::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - (k, asn1_bool.bool()) -} - -pub fn get_asn1_uint64<'a>(bytes: &'a [u8], oid_str: &str) -> (&'a [u8], u64) { - let (k, asn1_seq) = Sequence::from_der(bytes).unwrap(); - let (l, asn1_oid) = Oid::from_der(asn1_seq.content.as_ref()).unwrap(); - assert!(oid_str.eq(&asn1_oid.to_id_string())); - let (l, asn1_int) = Integer::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - (k, asn1_int.as_u64().unwrap()) -} - -pub fn get_asn1_bytes<'a>(bytes: &'a [u8], oid_str: &str) -> (&'a [u8], Vec) { - let (k, asn1_seq) = Sequence::from_der(bytes).unwrap(); - let (l, asn1_oid) = Oid::from_der(asn1_seq.content.as_ref()).unwrap(); - assert!(oid_str.eq(&asn1_oid.to_id_string())); - let (l, asn1_bytes) = OctetString::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - (k, asn1_bytes.into_cow().to_vec()) -} - -pub fn extract_sgx_extension<'a>(cert: &'a X509Certificate<'a>) -> SgxExtensions { - // https://download.01.org/intel-sgx/sgx-dcap/1.20/linux/docs/SGX_PCK_Certificate_CRL_Spec-1.4.pdf - - // : - // : - // : - // : - // : - // … - // : - // : - // : - // : - // : - // : - // : - // : - // : - // : - // : - - // SGX Extensions | 1.2.840.113741.1.13.1 | mandatory | ASN.1 Sequence - // PPID | 1.2.840.113741.1.13.1.1 | mandatory | ASN.1 Octet String - // TCB | 1.2.840.113741.1.13.1.2 | mandatory | ASN.1 Sequence - // SGX TCB Comp01 SVN | 1.2.840.113741.1.13.1.2.1 | mandatory | ASN.1 Integer - // SGX TCB Comp02 SVN | 1.2.840.113741.1.13.1.2.2 | mandatory | ASN.1 Integer - // ... - // SGX TCB Comp16 SVN | 1.2.840.113741.1.13.1.2.16 | mandatory | ASN.1 Integer - // PCESVN | 1.2.840.113741.1.13.1.2.17 | mandatory | ASN.1 Integer - // CPUSVN | 1.2.840.113741.1.13.1.2.18 | mandatory | ASN.1 Integer - // PCE-ID | 1.2.840.113741.1.13.1.3 | mandatory | ASN.1 Octet String - // FMSPC | 1.2.840.113741.1.13.1.4 | mandatory | ASN.1 Octet String - // SGX Type | 1.2.840.113741.1.13.1.5 | mandatory | ASN.1 Enumerated - // Platform Instance ID | 1.2.840.113741.1.13.1.6 | optional | ASN.1 Octet String - // Configuration | 1.2.840.113741.1.13.1.7 | optional | ASN.1 Sequence - // Dynamic Platform | 1.2.840.113741.1.13.1.7.1 | optional | ASN.1 Boolean - // Cached Keys | 1.2.840.113741.1.13.1.7.2 | optional | ASN.1 Boolean - // SMT Enabled | 1.2.840.113741.1.13.1.7.3 | optional | ASN.1 Boolean - - let sgx_extensions_bytes = cert - .get_extension_unique(&oid!(1.2.840 .113741 .1 .13 .1)) - .unwrap() - .unwrap() - .value; - - let (_, sgx_extensions) = Sequence::from_der(sgx_extensions_bytes).unwrap(); - - // we'll process the sgx extensions here... - let mut i = sgx_extensions.content.as_ref(); - - // let's define the required information to create the SgxExtensions struct - let mut ppid = [0; 16]; - let mut tcb = SgxExtensionTcbLevel { - sgxtcbcomp01svn: 0, - sgxtcbcomp02svn: 0, - sgxtcbcomp03svn: 0, - sgxtcbcomp04svn: 0, - sgxtcbcomp05svn: 0, - sgxtcbcomp06svn: 0, - sgxtcbcomp07svn: 0, - sgxtcbcomp08svn: 0, - sgxtcbcomp09svn: 0, - sgxtcbcomp10svn: 0, - sgxtcbcomp11svn: 0, - sgxtcbcomp12svn: 0, - sgxtcbcomp13svn: 0, - sgxtcbcomp14svn: 0, - sgxtcbcomp15svn: 0, - sgxtcbcomp16svn: 0, - pcesvn: 0, - cpusvn: [0; 16], - }; - let mut pceid = [0; 2]; - let mut fmspc = [0; 6]; - let mut sgx_type = 0; - let mut platform_instance_id: Option<[u8; 16]> = None; - let mut configuration: Option = None; - - while i.len() > 0 { - let (j, current_sequence) = Sequence::from_der(i).unwrap(); - i = j; - let (j, current_oid) = Oid::from_der(current_sequence.content.as_ref()).unwrap(); - match current_oid.to_id_string().as_str() { - "1.2.840.113741.1.13.1.1" => { - let (k, ppid_bytes) = OctetString::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - ppid.copy_from_slice(ppid_bytes.as_ref()); - } - "1.2.840.113741.1.13.1.2" => { - let (k, tcb_sequence) = Sequence::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - // iterate through from 1 - 18 - let (k, sgxtcbcomp01svn) = - get_asn1_uint64(tcb_sequence.content.as_ref(), "1.2.840.113741.1.13.1.2.1"); - let (k, sgxtcbcomp02svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.2"); - let (k, sgxtcbcomp03svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.3"); - let (k, sgxtcbcomp04svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.4"); - let (k, sgxtcbcomp05svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.5"); - let (k, sgxtcbcomp06svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.6"); - let (k, sgxtcbcomp07svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.7"); - let (k, sgxtcbcomp08svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.8"); - let (k, sgxtcbcomp09svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.9"); - let (k, sgxtcbcomp10svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.10"); - let (k, sgxtcbcomp11svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.11"); - let (k, sgxtcbcomp12svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.12"); - let (k, sgxtcbcomp13svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.13"); - let (k, sgxtcbcomp14svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.14"); - let (k, sgxtcbcomp15svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.15"); - let (k, sgxtcbcomp16svn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.16"); - let (k, pcesvn) = get_asn1_uint64(k, "1.2.840.113741.1.13.1.2.17"); - let (k, cpusvn) = get_asn1_bytes(k, "1.2.840.113741.1.13.1.2.18"); - - assert_eq!(k.len(), 0); - // copy the bytes into the tcb struct - tcb.sgxtcbcomp01svn = sgxtcbcomp01svn as u8; - tcb.sgxtcbcomp02svn = sgxtcbcomp02svn as u8; - tcb.sgxtcbcomp03svn = sgxtcbcomp03svn as u8; - tcb.sgxtcbcomp04svn = sgxtcbcomp04svn as u8; - tcb.sgxtcbcomp05svn = sgxtcbcomp05svn as u8; - tcb.sgxtcbcomp06svn = sgxtcbcomp06svn as u8; - tcb.sgxtcbcomp07svn = sgxtcbcomp07svn as u8; - tcb.sgxtcbcomp08svn = sgxtcbcomp08svn as u8; - tcb.sgxtcbcomp09svn = sgxtcbcomp09svn as u8; - tcb.sgxtcbcomp10svn = sgxtcbcomp10svn as u8; - tcb.sgxtcbcomp11svn = sgxtcbcomp11svn as u8; - tcb.sgxtcbcomp12svn = sgxtcbcomp12svn as u8; - tcb.sgxtcbcomp13svn = sgxtcbcomp13svn as u8; - tcb.sgxtcbcomp14svn = sgxtcbcomp14svn as u8; - tcb.sgxtcbcomp15svn = sgxtcbcomp15svn as u8; - tcb.sgxtcbcomp16svn = sgxtcbcomp16svn as u8; - tcb.pcesvn = pcesvn as u16; - tcb.cpusvn.copy_from_slice(cpusvn.as_ref()); - } - "1.2.840.113741.1.13.1.3" => { - let (k, pceid_bytes) = OctetString::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - pceid.copy_from_slice(pceid_bytes.as_ref()); - } - "1.2.840.113741.1.13.1.4" => { - let (k, fmspc_bytes) = OctetString::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - fmspc.copy_from_slice(fmspc_bytes.as_ref()); - } - "1.2.840.113741.1.13.1.5" => { - let (k, sgx_type_enum) = Enumerated::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - sgx_type = sgx_type_enum.0; - } - "1.2.840.113741.1.13.1.6" => { - let (k, platform_instance_id_bytes) = OctetString::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - let mut temp = [0; 16]; - temp.copy_from_slice(platform_instance_id_bytes.as_ref()); - platform_instance_id = Some(temp); - } - "1.2.840.113741.1.13.1.7" => { - let (k, configuration_seq) = Sequence::from_der(j).unwrap(); - assert_eq!(k.len(), 0); - let mut configuration_temp = PckPlatformConfiguration { - dynamic_platform: None, - cached_keys: None, - smt_enabled: None, - }; - // iterate through from 1 - 3, note that some of them might be optional. - let mut k = configuration_seq.content.as_ref(); - while k.len() > 0 { - let (l, asn1_seq) = Sequence::from_der(k).unwrap(); - k = l; - let (l, current_oid) = Oid::from_der(asn1_seq.content.as_ref()).unwrap(); - match current_oid.to_id_string().as_str() { - "1.2.840.113741.1.13.1.7.1" => { - let (l, dynamic_platform_bool) = Boolean::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - configuration_temp.dynamic_platform = - Some(dynamic_platform_bool.bool()); - } - "1.2.840.113741.1.13.1.7.2" => { - let (l, cached_keys_bool) = Boolean::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - configuration_temp.cached_keys = Some(cached_keys_bool.bool()); - } - "1.2.840.113741.1.13.1.7.3" => { - let (l, smt_enabled_bool) = Boolean::from_der(l).unwrap(); - assert_eq!(l.len(), 0); - configuration_temp.smt_enabled = Some(smt_enabled_bool.bool()); - } - _ => { - unreachable!("Unknown OID: {}", current_oid.to_id_string()); - } - } - } - // done parsing... - configuration = Some(configuration_temp); - } - _ => { - unreachable!("Unknown OID: {}", current_oid.to_id_string()); - } - } - } - - SgxExtensions { - ppid, - tcb, - pceid, - fmspc, - sgx_type, - platform_instance_id, - configuration, - } -} - -pub fn get_sgx_fmspc_tcbstatus_v2( - sgx_extensions: &SgxExtensions, - tcb_info_root: &TcbInfoV2, -) -> TcbStatus { - // we'll make sure the tcbinforoot is valid - // check that fmspc is valid - // check that pceid is valid - - // convert tcbinfo fmspc and pceid from string to bytes for comparison - assert!(sgx_extensions.fmspc.to_vec() == hex::decode(&tcb_info_root.tcb_info.fmspc).unwrap()); - assert!(sgx_extensions.pceid.to_vec() == hex::decode(&tcb_info_root.tcb_info.pce_id).unwrap()); - - // now that we are sure that fmspc and pceid is the same, we'll iterate through and find the tcbstatus - // we assume that the tcb_levels are sorted in descending svn order - // println!("sgx_extensions tcb: {:?}", sgx_extensions.tcb); - for tcb_level in tcb_info_root.tcb_info.tcb_levels.iter() { - let tcb = &tcb_level.tcb; - // println!("tcb: {:?}", tcb); - if tcb.sgxtcbcomp01svn <= sgx_extensions.tcb.sgxtcbcomp01svn - && tcb.sgxtcbcomp02svn <= sgx_extensions.tcb.sgxtcbcomp02svn - && tcb.sgxtcbcomp03svn <= sgx_extensions.tcb.sgxtcbcomp03svn - && tcb.sgxtcbcomp04svn <= sgx_extensions.tcb.sgxtcbcomp04svn - && tcb.sgxtcbcomp05svn <= sgx_extensions.tcb.sgxtcbcomp05svn - && tcb.sgxtcbcomp06svn <= sgx_extensions.tcb.sgxtcbcomp06svn - && tcb.sgxtcbcomp07svn <= sgx_extensions.tcb.sgxtcbcomp07svn - && tcb.sgxtcbcomp08svn <= sgx_extensions.tcb.sgxtcbcomp08svn - && tcb.sgxtcbcomp09svn <= sgx_extensions.tcb.sgxtcbcomp09svn - && tcb.sgxtcbcomp10svn <= sgx_extensions.tcb.sgxtcbcomp10svn - && tcb.sgxtcbcomp11svn <= sgx_extensions.tcb.sgxtcbcomp11svn - && tcb.sgxtcbcomp12svn <= sgx_extensions.tcb.sgxtcbcomp12svn - && tcb.sgxtcbcomp13svn <= sgx_extensions.tcb.sgxtcbcomp13svn - && tcb.sgxtcbcomp14svn <= sgx_extensions.tcb.sgxtcbcomp14svn - && tcb.sgxtcbcomp15svn <= sgx_extensions.tcb.sgxtcbcomp15svn - && tcb.sgxtcbcomp16svn <= sgx_extensions.tcb.sgxtcbcomp16svn - && tcb.pcesvn <= sgx_extensions.tcb.pcesvn - { - // println!("tcb_status: {:?}", tcb_level.tcb_status); - return TcbStatus::from_str(tcb_level.tcb_status.as_str()); - } - } - // we went through all the tcblevels and didn't find a match - // shouldn't happen so we'll toggle an exception - unreachable!(); -} - -// Slightly modified from https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/7e5b2a13ca5472de8d97dd7d7024c2ea5af9a6ba/Src/AttestationLibrary/src/Verifiers/Checks/TcbLevelCheck.cpp#L129-L181 -pub fn get_sgx_tdx_fmspc_tcbstatus_v3( - tee_type: u32, - sgx_extensions: &SgxExtensions, - tee_tcb_svn: &[u8; 16], - tcbinfov3: &TcbInfoV3, -) -> (TcbStatus, TcbStatus, Option>) { - // we'll make sure the tcbinforoot is valid - // check that fmspc is valid - // check that pceid is valid - - // convert tcbinfo fmspc and pceid from string to bytes for comparison - assert!(sgx_extensions.fmspc.to_vec() == hex::decode(&tcbinfov3.tcb_info.fmspc).unwrap()); - assert!(sgx_extensions.pceid.to_vec() == hex::decode(&tcbinfov3.tcb_info.pce_id).unwrap()); - - let mut sgx_tcb_status = TcbStatus::TcbUnrecognized; - let mut tdx_tcb_status = TcbStatus::TcbUnrecognized; - - let extension_pcesvn = sgx_extensions.tcb.pcesvn; - let mut advisory_ids = None; - - for tcb_level in tcbinfov3.tcb_info.tcb_levels.iter() { - if sgx_tcb_status == TcbStatus::TcbUnrecognized { - let sgxtcbcomponents_ok = - match_sgxtcbcomp(sgx_extensions, &tcb_level.tcb.sgxtcbcomponents); - let pcesvn_ok = extension_pcesvn >= tcb_level.tcb.pcesvn; - if sgxtcbcomponents_ok && pcesvn_ok { - sgx_tcb_status = TcbStatus::from_str(tcb_level.tcb_status.as_str()); - if tee_type == SGX_TEE_TYPE { - advisory_ids = tcb_level.advisory_ids.clone(); - } - } - } - if sgx_tcb_status != TcbStatus::TcbUnrecognized || sgx_tcb_status != TcbStatus::TcbRevoked { - if !is_empty(tee_tcb_svn) { - let tdxtcbcomponents_ok = match tcb_level.tcb.tdxtcbcomponents.as_ref() { - Some(tdxtcbcomponents) => tdxtcbcomponents - .iter() - .zip(tee_tcb_svn.iter()) - .all(|(tcb, tee)| *tee >= tcb.svn as u8), - None => true, - }; - if tdxtcbcomponents_ok { - tdx_tcb_status = TcbStatus::from_str(tcb_level.tcb_status.as_str()); - if tee_type == TDX_TEE_TYPE { - advisory_ids = tcb_level.advisory_ids.clone(); - } - break; - } - } - } - } - (sgx_tcb_status, tdx_tcb_status, advisory_ids) -} - -fn is_empty(slice: &[u8]) -> bool { - slice.iter().all(|&x| x == 0) -} - -fn match_sgxtcbcomp(sgx_extensions: &SgxExtensions, sgxtcbcomponents: &[TcbComponent]) -> bool { - let extension_tcbcomponents = extension_to_tcbcomponents(&sgx_extensions.tcb); - // Compare all of the SGX TCB Comp SVNs retrieved from the SGX PCK Certificate (from 01 to 16) with the corresponding values of SVNs in sgxtcbcomponents array of TCB Level. - // If all SGX TCB Comp SVNs in the certificate are greater or equal to the corresponding values in TCB Level, then return true. - // Otherwise, return false. - extension_tcbcomponents - .iter() - .zip(sgxtcbcomponents.iter()) - .all(|(ext, tcb)| ext.svn >= tcb.svn) -} - -fn extension_to_tcbcomponents(extension: &SgxExtensionTcbLevel) -> Vec { - let mut tcbcomponents = Vec::with_capacity(16); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp01svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp02svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp03svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp04svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp05svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp06svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp07svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp08svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp09svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp10svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp11svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp12svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp13svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp14svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp15svn, - category: None, - type_: None, - }); - tcbcomponents.push(TcbComponent { - svn: extension.sgxtcbcomp16svn, - category: None, - type_: None, - }); - - tcbcomponents -} diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs deleted file mode 100644 index 0eec46b..0000000 --- a/src/utils/crypto.rs +++ /dev/null @@ -1,19 +0,0 @@ -use p256::ecdsa::{VerifyingKey, signature::Verifier, Signature}; - -// verify_p256_signature_bytes verifies a P256 ECDSA signature -// using the provided data, signature, and public key. -// The data is the message that was signed as a byte slice. -// The signature is the signature (in raw form [r][s]) of the data as a byte slice. (64 bytes) -// The public_key is the public key (in uncompressed form [4][x][y]) of the entity that signed the data. (65 bytes) -// Returns true if the signature is valid, false otherwise. -pub fn verify_p256_signature_bytes(data: &[u8], signature: &[u8], public_key: &[u8]) -> bool { - let signature = Signature::from_bytes(signature.try_into().unwrap()).unwrap(); - let verifying_key = VerifyingKey::from_sec1_bytes(public_key).unwrap(); - verifying_key.verify(data, &signature).is_ok() -} - -pub fn verify_p256_signature_der(data: &[u8], signature: &[u8], public_key: &[u8]) -> bool { - let signature = Signature::from_der(signature).unwrap(); - let verifying_key = VerifyingKey::from_sec1_bytes(public_key).unwrap(); - verifying_key.verify(data, &signature).is_ok() -} \ No newline at end of file diff --git a/src/utils/enclave_identity.rs b/src/utils/enclave_identity.rs deleted file mode 100644 index 5a5c6a5..0000000 --- a/src/utils/enclave_identity.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::types::{enclave_identity::EnclaveIdentityV2, quotes::body::EnclaveReport, TcbStatus}; -use crate::utils::crypto::verify_p256_signature_bytes; -use sha3::{Digest, Keccak256}; - -use crate::X509Certificate; - -pub fn validate_enclave_identityv2( - enclave_identityv2: &EnclaveIdentityV2, - sgx_signing_pubkey: &X509Certificate, - current_time: u64, -) -> bool { - // get tcb_info_root time - let issue_date = - chrono::DateTime::parse_from_rfc3339(&enclave_identityv2.enclave_identity.issue_date) - .unwrap(); - let next_update_date = - chrono::DateTime::parse_from_rfc3339(&enclave_identityv2.enclave_identity.next_update) - .unwrap(); - - // convert the issue_date and next_update_date to seconds since epoch - let issue_date_seconds = issue_date.timestamp() as u64; - let next_update_seconds = next_update_date.timestamp() as u64; - - // check that the current time is between the issue_date and next_update_date - if current_time < issue_date_seconds || current_time > next_update_seconds { - return false; - } - - // signature is a hex string, we'll convert it to bytes - // ZL: we'll assume that the signature is a P256 ECDSA signature - let enclave_identityv2_signature_bytes = hex::decode(&enclave_identityv2.signature).unwrap(); - - // verify that the enclave_identity_root is signed by the root cert - let enclave_identityv2_signature_data = - serde_json::to_vec(&enclave_identityv2.enclave_identity).unwrap(); - verify_p256_signature_bytes( - &enclave_identityv2_signature_data, - &enclave_identityv2_signature_bytes, - sgx_signing_pubkey.public_key().subject_public_key.as_ref(), - ) -} - -pub fn get_qe_tcbstatus( - enclave_report: &EnclaveReport, - qeidentityv2: &EnclaveIdentityV2, -) -> TcbStatus { - for tcb_level in qeidentityv2.enclave_identity.tcb_levels.iter() { - if tcb_level.tcb.isvsvn <= enclave_report.isv_svn { - let tcb_status = match &tcb_level.tcb_status[..] { - "UpToDate" => TcbStatus::OK, - "SWHardeningNeeded" => TcbStatus::TcbSwHardeningNeeded, - "ConfigurationAndSWHardeningNeeded" => { - TcbStatus::TcbConfigurationAndSwHardeningNeeded - } - "ConfigurationNeeded" => TcbStatus::TcbConfigurationNeeded, - "OutOfDate" => TcbStatus::TcbOutOfDate, - "OutOfDateConfigurationNeeded" => TcbStatus::TcbOutOfDateConfigurationNeeded, - "Revoked" => TcbStatus::TcbRevoked, - _ => TcbStatus::TcbUnrecognized, - }; - return tcb_status; - } - } - - TcbStatus::TcbUnrecognized -} - -// A content_hash is the hash representation of the enclave_identity -// excluding both "issue_date" and "next_update" fields -// integers are big-endian encoded -pub fn get_enclave_identityv2_content_hash(enclave_identityv2: &EnclaveIdentityV2) -> [u8; 32] { - let mut pre_image: Vec = vec![]; - pre_image.extend_from_slice(&[convert_enclave_identity_id_string_to_u8( - &enclave_identityv2.enclave_identity.id, - )]); - pre_image.extend_from_slice(&enclave_identityv2.enclave_identity.version.to_be_bytes()); - pre_image.extend_from_slice( - &enclave_identityv2 - .enclave_identity - .tcb_evaluation_data_number - .to_be_bytes(), - ); - pre_image.extend_from_slice( - hex::decode(&enclave_identityv2.enclave_identity.miscselect) - .unwrap() - .as_slice(), - ); - pre_image.extend_from_slice( - hex::decode(&enclave_identityv2.enclave_identity.miscselect_mask) - .unwrap() - .as_slice(), - ); - pre_image.extend_from_slice( - hex::decode(&enclave_identityv2.enclave_identity.attributes) - .unwrap() - .as_slice(), - ); - pre_image.extend_from_slice( - hex::decode(&enclave_identityv2.enclave_identity.attributes_mask) - .unwrap() - .as_slice(), - ); - pre_image.extend_from_slice( - hex::decode(&enclave_identityv2.enclave_identity.mrsigner) - .unwrap() - .as_slice(), - ); - pre_image.extend_from_slice(&enclave_identityv2.enclave_identity.isvprodid.to_be_bytes()); - pre_image.extend_from_slice( - serde_json::to_vec(&enclave_identityv2.enclave_identity.tcb_levels) - .unwrap() - .as_slice(), - ); - Keccak256::digest(&pre_image).try_into().unwrap() -} - -fn convert_enclave_identity_id_string_to_u8(id_str: &str) -> u8 { - match id_str { - "QE" => 0, - "QVE" => 1, - "TD_QE" => 2, - _ => panic!("Unknown enclave_identity id string"), - } -} diff --git a/src/utils/hash.rs b/src/utils/hash.rs deleted file mode 100644 index f48cda5..0000000 --- a/src/utils/hash.rs +++ /dev/null @@ -1,20 +0,0 @@ -use sha2::{Sha256, Digest}; -use sha3::Keccak256; - -pub fn sha256sum(data: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(data); - let result = hasher.finalize(); - let mut output = [0; 32]; - output.copy_from_slice(&result); - output -} - -pub fn keccak256sum(data: &[u8]) -> [u8; 32] { - let mut hasher = Keccak256::new(); - hasher.update(data); - let result = hasher.finalize(); - let mut output = [0; 32]; - output.copy_from_slice(&result); - output -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 0a2103c..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod hash; -pub mod cert; -pub mod crypto; -pub mod tcbinfo; -pub mod enclave_identity; -pub mod quotes; -pub mod tdx_module; diff --git a/src/utils/quotes/mod.rs b/src/utils/quotes/mod.rs deleted file mode 100644 index 6c5a97c..0000000 --- a/src/utils/quotes/mod.rs +++ /dev/null @@ -1,300 +0,0 @@ -pub mod version_3; -pub mod version_4; - -use x509_parser::certificate::X509Certificate; - -use crate::constants::{ECDSA_256_WITH_P256_CURVE, INTEL_QE_VENDOR_ID}; -use crate::types::enclave_identity::EnclaveIdentityV2; -use crate::utils::hash::sha256sum; - -use crate::types::cert::{IntelSgxCrls, SgxExtensions}; -use crate::types::collaterals::IntelCollateral; -use crate::types::quotes::{ - body::{EnclaveReport, QuoteBody}, - CertData, QuoteHeader, -}; -use crate::types::tcbinfo::TcbInfo; -use crate::types::TcbStatus; -use crate::utils::enclave_identity::get_qe_tcbstatus; - -use crate::utils::cert::{ - extract_sgx_extension, get_x509_issuer_cn, get_x509_subject_cn, parse_certchain, parse_pem, - verify_certchain_signature, verify_certificate, verify_crl, -}; -use crate::utils::crypto::verify_p256_signature_bytes; -use crate::utils::enclave_identity::validate_enclave_identityv2; -use crate::utils::tcbinfo::{validate_tcbinfov2, validate_tcbinfov3}; - -fn check_quote_header(quote_header: &QuoteHeader, quote_version: u16) -> bool { - let quote_version_is_valid = quote_header.version == quote_version; - let att_key_type_is_supported = quote_header.att_key_type == ECDSA_256_WITH_P256_CURVE; - let qe_vendor_id_is_valid = quote_header.qe_vendor_id == INTEL_QE_VENDOR_ID; - - quote_version_is_valid && att_key_type_is_supported && qe_vendor_id_is_valid -} - -// verification steps that are required for both SGX and TDX quotes -// Checks: -// - valid qeidentity -// - valid tcbinfo -// - valid pck certificate chain -// - qe report content -// - ecdsa verification on qe report data and quote body data -// Returns: -// - QEIdentity TCB Status -// - SGX Extension -// - TCBInfo (v2 or v3) -fn common_verify_and_fetch_tcb( - quote_header: &QuoteHeader, - quote_body: &QuoteBody, - ecdsa_attestation_signature: &[u8], - ecdsa_attestation_pubkey: &[u8], - qe_report: &EnclaveReport, - qe_report_signature: &[u8], - qe_auth_data: &[u8], - qe_cert_data: &CertData, - collaterals: &IntelCollateral, - current_time: u64, -) -> (TcbStatus, SgxExtensions, TcbInfo) { - let signing_cert = collaterals.get_sgx_tcb_signing(); - let intel_sgx_root_cert = collaterals.get_sgx_intel_root_ca(); - - // verify that signing_verifying_key is not revoked and signed by the root cert - let intel_crls = IntelSgxCrls::from_collaterals(collaterals); - - // ZL: If collaterals are checked by the caller, then these can be removed - // check that CRLs are valid - match &intel_crls.sgx_root_ca_crl { - Some(crl) => { - assert!(verify_crl(crl, &intel_sgx_root_cert, current_time)); - } - None => { - panic!("No SGX Root CA CRL found"); - } - } - - let signing_cert_revoked = intel_crls.is_cert_revoked(&signing_cert); - assert!(!signing_cert_revoked, "TCB Signing Cert revoked"); - assert!( - verify_certificate(&signing_cert, &intel_sgx_root_cert, current_time), - "TCB Signing Cert is not signed by Intel SGX Root CA" - ); - - // validate QEIdentity - let qeidentityv2 = collaterals.get_qeidentityv2(); - assert!(validate_enclave_identityv2( - &qeidentityv2, - &signing_cert, - current_time - )); - - // verify QEReport then get TCB Status - assert!( - verify_qe_report_data( - &qe_report.report_data, - &ecdsa_attestation_pubkey, - qe_auth_data - ), - "QE Report Data is incorrect" - ); - assert!( - validate_qe_report(qe_report, &qeidentityv2), - "QE Report values do not match with the provided QEIdentity" - ); - let qe_tcb_status = get_qe_tcbstatus(qe_report, &qeidentityv2); - assert!( - qe_tcb_status != TcbStatus::TcbRevoked, - "QEIdentity TCB Revoked" - ); - - // get the certchain embedded in the ecda quote signature data - // this can be one of 5 types - // we only handle type 5 for now... - // TODO: Add support for all other types - assert_eq!(qe_cert_data.cert_data_type, 5, "QE Cert Type must be 5"); - let certchain_pems = parse_pem(&qe_cert_data.cert_data).unwrap(); - let certchain = parse_certchain(&certchain_pems); - // checks that the certificates used in the certchain are not revoked - for cert in certchain.iter() { - assert!(!intel_crls.is_cert_revoked(cert)); - } - - // get the pck certificate, and check whether issuer common name is valid - let pck_cert = &certchain[0]; - let pck_cert_issuer = &certchain[1]; - assert!( - check_pck_issuer_and_crl(pck_cert, pck_cert_issuer, &intel_crls, current_time), - "Invalid PCK Issuer or CRL" - ); - - // verify that the cert chain signatures are valid - assert!( - verify_certchain_signature(&certchain, &intel_sgx_root_cert, current_time), - "Invalid PCK Chain" - ); - - // verify the signature for qe report data - let qe_report_bytes = qe_report.to_bytes(); - - let qe_report_public_key = pck_cert.public_key().subject_public_key.as_ref(); - assert!( - verify_p256_signature_bytes(&qe_report_bytes, qe_report_signature, qe_report_public_key), - "Invalid qe signature" - ); - - // get the SGX extension - let sgx_extensions = extract_sgx_extension(&pck_cert); - - // verify the signature for attestation body - let mut data = Vec::new(); - data.extend_from_slice("e_header.to_bytes()); - match quote_body { - QuoteBody::SGXQuoteBody(body) => data.extend_from_slice(&body.to_bytes()), - QuoteBody::TD10QuoteBody(body) => data.extend_from_slice(&body.to_bytes()), - }; - - // prefix pub key - let mut prefixed_pub_key = [4; 65]; - prefixed_pub_key[1..65].copy_from_slice(ecdsa_attestation_pubkey); - assert!( - verify_p256_signature_bytes(&data, ecdsa_attestation_signature, &prefixed_pub_key), - "Invalid attestation signature" - ); - - // validate tcbinfo v2 or v3, depending on the quote version - let tcb_info: TcbInfo; - if quote_header.version >= 4 { - let tcb_info_v3 = collaterals.get_tcbinfov3(); - assert!( - validate_tcbinfov3(&tcb_info_v3, &signing_cert, current_time), - "Invalid TCBInfoV3" - ); - tcb_info = TcbInfo::V3(tcb_info_v3); - } else { - let tcb_info_v2 = collaterals.get_tcbinfov2(); - assert!( - validate_tcbinfov2(&tcb_info_v2, &signing_cert, current_time), - "Invalid TCBInfoV2" - ); - tcb_info = TcbInfo::V2(tcb_info_v2); - } - - (qe_tcb_status, sgx_extensions, tcb_info) -} - -fn check_pck_issuer_and_crl( - pck_cert: &X509Certificate, - pck_issuer_cert: &X509Certificate, - intel_crls: &IntelSgxCrls, - current_time: u64 -) -> bool { - // we'll check what kind of cert is it, and validate the appropriate CRL - let pck_cert_subject_cn = get_x509_issuer_cn(pck_cert); - let pck_cert_issuer_cn = get_x509_subject_cn(pck_issuer_cert); - - assert!( - pck_cert_issuer_cn == pck_cert_subject_cn, - "PCK Issuer CN does not match with PCK Intermediate Subject CN" - ); - - match pck_cert_issuer_cn.as_str() { - "Intel SGX PCK Platform CA" => verify_crl( - intel_crls.sgx_pck_platform_crl.as_ref().unwrap(), - pck_issuer_cert, - current_time - ), - "Intel SGX PCK Processor CA" => verify_crl( - &intel_crls.sgx_pck_processor_crl.as_ref().unwrap(), - pck_issuer_cert, - current_time - ), - _ => { - panic!("Unknown PCK Cert Subject CN: {}", pck_cert_subject_cn); - } - } -} - -fn validate_qe_report(enclave_report: &EnclaveReport, qeidentityv2: &EnclaveIdentityV2) -> bool { - // make sure that the enclave_identityv2 is a qeidentityv2 - // check that id is "QE", "TD_QE" or "QVE" and version is 2 - if !((qeidentityv2.enclave_identity.id == "QE" - || qeidentityv2.enclave_identity.id == "TD_QE" - || qeidentityv2.enclave_identity.id == "QVE") - && qeidentityv2.enclave_identity.version == 2) - { - return false; - } - - let mrsigner_ok = enclave_report.mrsigner - == hex::decode(&qeidentityv2.enclave_identity.mrsigner) - .unwrap() - .as_slice(); - let isvprodid_ok = enclave_report.isv_prod_id == qeidentityv2.enclave_identity.isvprodid; - - let attributes = hex::decode(&qeidentityv2.enclave_identity.attributes).unwrap(); - let attributes_mask = hex::decode(&qeidentityv2.enclave_identity.attributes_mask).unwrap(); - let masked_attributes = attributes - .iter() - .zip(attributes_mask.iter()) - .map(|(a, m)| a & m) - .collect::>(); - let masked_enclave_attributes = enclave_report - .attributes - .iter() - .zip(attributes_mask.iter()) - .map(|(a, m)| a & m) - .collect::>(); - let enclave_attributes_ok = masked_enclave_attributes == masked_attributes; - - let miscselect = hex::decode(&qeidentityv2.enclave_identity.miscselect).unwrap(); - let miscselect_mask = hex::decode(&qeidentityv2.enclave_identity.miscselect_mask).unwrap(); - let masked_miscselect = miscselect - .iter() - .zip(miscselect_mask.iter()) - .map(|(a, m)| a & m) - .collect::>(); - let masked_enclave_miscselect = enclave_report - .misc_select - .iter() - .zip(miscselect_mask.iter()) - .map(|(a, m)| a & m) - .collect::>(); - let enclave_miscselect_ok = masked_enclave_miscselect == masked_miscselect; - - mrsigner_ok && isvprodid_ok && enclave_attributes_ok && enclave_miscselect_ok -} - -fn verify_qe_report_data( - report_data: &[u8], - ecdsa_attestation_key: &[u8], - qe_auth_data: &[u8], -) -> bool { - let mut verification_data = Vec::new(); - verification_data.extend_from_slice(ecdsa_attestation_key); - verification_data.extend_from_slice(qe_auth_data); - let mut recomputed_report_data = [0u8; 64]; - recomputed_report_data[..32].copy_from_slice(&sha256sum(&verification_data)); - recomputed_report_data == report_data -} - -// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp#L271-L312 -fn converge_tcb_status_with_qe_tcb(tcb_status: TcbStatus, qe_tcb_status: TcbStatus) -> TcbStatus { - let converged_tcb_status: TcbStatus; - match qe_tcb_status { - TcbStatus::TcbOutOfDate => { - if tcb_status == TcbStatus::OK || tcb_status == TcbStatus::TcbSwHardeningNeeded { - converged_tcb_status = TcbStatus::TcbOutOfDate; - } else if tcb_status == TcbStatus::TcbConfigurationNeeded - || tcb_status == TcbStatus::TcbConfigurationAndSwHardeningNeeded - { - converged_tcb_status = TcbStatus::TcbOutOfDateConfigurationNeeded; - } else { - converged_tcb_status = tcb_status; - } - }, - _ => { - converged_tcb_status = tcb_status; - } - } - converged_tcb_status -} diff --git a/src/utils/quotes/version_3.rs b/src/utils/quotes/version_3.rs deleted file mode 100644 index 510b8a4..0000000 --- a/src/utils/quotes/version_3.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::types::quotes::{body::QuoteBody, version_3::QuoteV3}; -use crate::types::{ - collaterals::IntelCollateral, - tcbinfo::{TcbInfo, TcbInfoV2}, - TcbStatus, VerifiedOutput, -}; -use crate::utils::cert::get_sgx_fmspc_tcbstatus_v2; - -use super::{check_quote_header, common_verify_and_fetch_tcb, converge_tcb_status_with_qe_tcb}; - -pub fn verify_quote_dcapv3( - quote: &QuoteV3, - collaterals: &IntelCollateral, - current_time: u64, -) -> VerifiedOutput { - assert!(check_quote_header("e.header, 3), "invalid quote header"); - - let quote_body = QuoteBody::SGXQuoteBody(quote.isv_enclave_report); - let (qe_tcb_status, sgx_extensions, tcb_info) = common_verify_and_fetch_tcb( - "e.header, - "e_body, - "e.signature.isv_enclave_report_signature, - "e.signature.ecdsa_attestation_key, - "e.signature.qe_report, - "e.signature.qe_report_signature, - "e.signature.qe_auth_data.data, - "e.signature.qe_cert_data, - collaterals, - current_time, - ); - - let tcb_info_v2: TcbInfoV2; - if let TcbInfo::V2(tcb) = tcb_info { - tcb_info_v2 = tcb; - } else { - panic!("TcbInfo must be V2!"); - } - let mut tcb_status = get_sgx_fmspc_tcbstatus_v2(&sgx_extensions, &tcb_info_v2); - - assert!(tcb_status != TcbStatus::TcbRevoked, "FMSPC TCB Revoked"); - - tcb_status = converge_tcb_status_with_qe_tcb(tcb_status, qe_tcb_status); - - VerifiedOutput { - quote_version: quote.header.version, - tee_type: quote.header.tee_type, - tcb_status, - fmspc: sgx_extensions.fmspc, - quote_body: quote_body, - advisory_ids: None, - } -} diff --git a/src/utils/quotes/version_4.rs b/src/utils/quotes/version_4.rs deleted file mode 100644 index 19cbe90..0000000 --- a/src/utils/quotes/version_4.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::constants::SGX_TEE_TYPE; -use crate::types::quotes::body::QuoteBody; -use crate::types::quotes::{version_4::QuoteV4, CertDataType}; -use crate::types::TcbStatus; -use crate::types::{ - tcbinfo::{TcbInfo, TcbInfoV3}, - collaterals::IntelCollateral, VerifiedOutput, -}; -use crate::utils::cert::get_sgx_tdx_fmspc_tcbstatus_v3; -use crate::utils::tdx_module::{ - converge_tcb_status_with_tdx_module_tcb, get_tdx_module_identity_and_tcb, -}; - -use super::{check_quote_header, common_verify_and_fetch_tcb, converge_tcb_status_with_qe_tcb}; - -pub fn verify_quote_dcapv4( - quote: &QuoteV4, - collaterals: &IntelCollateral, - current_time: u64, -) -> VerifiedOutput { - assert!(check_quote_header("e.header, 4), "invalid quote header"); - - // we'll now proceed to verify the qe - let qe_cert_data_v4 = "e.signature.qe_cert_data; - - // right now we just handle type 6, which contains the QEReport, QEReportSignature, QEAuthData and another CertData - let qe_report_cert_data = if let CertDataType::QeReportCertData(qe_report_cert_data) = - qe_cert_data_v4.get_cert_data() - { - qe_report_cert_data - } else { - panic!("Unsupported CertDataType in QuoteSignatureDataV4"); - }; - - let (qe_tcb_status, sgx_extensions, tcb_info) = common_verify_and_fetch_tcb( - "e.header, - "e.quote_body, - "e.signature.quote_signature, - "e.signature.ecdsa_attestation_key, - &qe_report_cert_data.qe_report, - &qe_report_cert_data.qe_report_signature, - &qe_report_cert_data.qe_auth_data.data, - &qe_report_cert_data.qe_cert_data, - collaterals, - current_time, - ); - - let tcb_info_v3: TcbInfoV3; - if let TcbInfo::V3(tcb) = tcb_info { - tcb_info_v3 = tcb; - } else { - panic!("TcbInfo must be V3!"); - } - - let (quote_tdx_body, tee_tcb_svn) = if let QuoteBody::TD10QuoteBody(body) = "e.quote_body { - (Some(body), body.tee_tcb_svn) - } else { - // SGX does not produce tee_tcb_svns - (None, [0; 16]) - }; - - let tee_type = quote.header.tee_type; - let (sgx_tcb_status, tdx_tcb_status, advisory_ids) = - get_sgx_tdx_fmspc_tcbstatus_v3(tee_type, &sgx_extensions, &tee_tcb_svn, &tcb_info_v3); - - assert!( - sgx_tcb_status != TcbStatus::TcbRevoked || tdx_tcb_status != TcbStatus::TcbRevoked, - "FMSPC TCB Revoked" - ); - - let mut tcb_status: TcbStatus; - if quote.header.tee_type == SGX_TEE_TYPE { - tcb_status = sgx_tcb_status; - } else { - tcb_status = tdx_tcb_status; - - // Fetch TDXModule TCB and TDXModule Identity - let (tdx_module_tcb_status, tdx_module_mrsigner, tdx_module_attributes) = - get_tdx_module_identity_and_tcb(&tee_tcb_svn, &tcb_info_v3); - - assert!( - tdx_module_tcb_status != TcbStatus::TcbRevoked, - "TDX Module TCB Revoked" - ); - - // check TDX module - let (tdx_report_mrsigner, tdx_report_attributes) = if let Some(tdx_body) = quote_tdx_body { - (tdx_body.mrsignerseam, tdx_body.seam_attributes) - } else { - unreachable!(); - }; - - let mr_signer_matched = tdx_module_mrsigner == tdx_report_mrsigner; - let attributes_matched = tdx_module_attributes == tdx_report_attributes; - assert!( - mr_signer_matched && attributes_matched, - "TDX module values mismatch" - ); - - tcb_status = converge_tcb_status_with_tdx_module_tcb(tcb_status, tdx_module_tcb_status) - } - - tcb_status = converge_tcb_status_with_qe_tcb(tcb_status, qe_tcb_status); - - VerifiedOutput { - quote_version: quote.header.version, - tee_type: quote.header.tee_type, - tcb_status, - fmspc: sgx_extensions.fmspc, - quote_body: quote.quote_body, - advisory_ids: advisory_ids - } -} diff --git a/src/utils/tcbinfo.rs b/src/utils/tcbinfo.rs deleted file mode 100644 index 1b97f6b..0000000 --- a/src/utils/tcbinfo.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::types::tcbinfo::{TcbInfoV2, TcbInfoV3}; -use crate::utils::crypto::verify_p256_signature_bytes; -use crate::X509Certificate; - -use sha3::{Digest, Keccak256}; - -pub fn validate_tcbinfov2( - tcbinfov2: &TcbInfoV2, - sgx_signing_cert: &X509Certificate, - current_time: u64, -) -> bool { - // get tcb_info_root time - let issue_date = chrono::DateTime::parse_from_rfc3339(&tcbinfov2.tcb_info.issue_date).unwrap(); - let next_update_date = - chrono::DateTime::parse_from_rfc3339(&tcbinfov2.tcb_info.next_update).unwrap(); - - // convert the issue_date and next_update_date to seconds since epoch - let issue_date_seconds = issue_date.timestamp() as u64; - let next_update_seconds = next_update_date.timestamp() as u64; - - // check that the current time is between the issue_date and next_update_date - if current_time < issue_date_seconds || current_time > next_update_seconds { - return false; - } - - // signature is a hex string, we'll convert it to bytes - // ZL: we'll assume that the signature is a P256 ECDSA signature - let tcbinfov2_signature_bytes = hex::decode(&tcbinfov2.signature).unwrap(); - - // verify that the tcb_info_root is signed by the root cert - let tcbinfov2_signature_data = serde_json::to_vec(&tcbinfov2.tcb_info).unwrap(); - verify_p256_signature_bytes( - &tcbinfov2_signature_data, - &tcbinfov2_signature_bytes, - sgx_signing_cert.public_key().subject_public_key.as_ref(), - ) -} - -pub fn validate_tcbinfov3( - tcbinfov3: &TcbInfoV3, - sgx_signing_cert: &X509Certificate, - current_time: u64, -) -> bool { - // get tcb_info_root time - let issue_date = chrono::DateTime::parse_from_rfc3339(&tcbinfov3.tcb_info.issue_date).unwrap(); - let next_update_date = - chrono::DateTime::parse_from_rfc3339(&tcbinfov3.tcb_info.next_update).unwrap(); - - // convert the issue_date and next_update_date to seconds since epoch - let issue_date_seconds = issue_date.timestamp() as u64; - let next_update_seconds = next_update_date.timestamp() as u64; - - // check that the current time is between the issue_date and next_update_date - if current_time < issue_date_seconds || current_time > next_update_seconds { - assert!(false); - return false; - } - - // signature is a hex string, we'll convert it to bytes - // ZL: we'll assume that the signature is a P256 ECDSA signature - let tcbinfov3_signature_bytes = hex::decode(&tcbinfov3.signature).unwrap(); - - // verify that the tcb_info_root is signed by the root cert - let tcbinfov3_signature_data = serde_json::to_vec(&tcbinfov3.tcb_info).unwrap(); - verify_p256_signature_bytes( - &tcbinfov3_signature_data, - &tcbinfov3_signature_bytes, - sgx_signing_cert.public_key().subject_public_key.as_ref(), - ) -} - -// A content_hash is the hash representation of tcb_info -// excluding both "issue_date" and "next_update" fields -// integers are big-endian encoded - -pub fn get_tcbinfov2_content_hash(tcbinfov2: &TcbInfoV2) -> [u8; 32] { - let mut pre_image: Vec = vec![]; - - pre_image.extend_from_slice(&[tcbinfov2.tcb_info.tcb_type]); - pre_image.extend_from_slice(&[tcb_id_string_to_u8("SGX")]); // SGX by default for V2 - pre_image.extend_from_slice(&tcbinfov2.tcb_info.version.to_be_bytes()); - pre_image.extend_from_slice(&tcbinfov2.tcb_info.tcb_evaluation_data_number.to_be_bytes()); - pre_image.extend_from_slice(hex::decode(&tcbinfov2.tcb_info.fmspc).unwrap().as_slice()); - pre_image.extend_from_slice(hex::decode(&tcbinfov2.tcb_info.pce_id).unwrap().as_slice()); - pre_image.extend_from_slice( - serde_json::to_vec(&tcbinfov2.tcb_info.tcb_levels) - .unwrap() - .as_slice(), - ); - - Keccak256::digest(&pre_image).try_into().unwrap() -} - -pub fn get_tcbinfov3_content_hash(tcbinfov3: &TcbInfoV3) -> [u8; 32] { - let mut pre_image: Vec = vec![]; - - pre_image.extend_from_slice(&[tcbinfov3.tcb_info.tcb_type]); - pre_image.extend_from_slice(&[tcb_id_string_to_u8(&tcbinfov3.tcb_info.id)]); // SGX by default for V2 - pre_image.extend_from_slice(&tcbinfov3.tcb_info.version.to_be_bytes()); - pre_image.extend_from_slice(&tcbinfov3.tcb_info.tcb_evaluation_data_number.to_be_bytes()); - pre_image.extend_from_slice(hex::decode(&tcbinfov3.tcb_info.fmspc).unwrap().as_slice()); - pre_image.extend_from_slice(hex::decode(&tcbinfov3.tcb_info.pce_id).unwrap().as_slice()); - pre_image.extend_from_slice( - serde_json::to_vec(&tcbinfov3.tcb_info.tcb_levels) - .unwrap() - .as_slice(), - ); - - if let Some(tdx_module) = &tcbinfov3.tcb_info.tdx_module { - pre_image.extend_from_slice(serde_json::to_vec(tdx_module).unwrap().as_slice()); - } - - if let Some(tdx_module_identities) = &tcbinfov3.tcb_info.tdx_module_identities { - pre_image.extend_from_slice( - serde_json::to_vec(tdx_module_identities) - .unwrap() - .as_slice(), - ); - } - - Keccak256::digest(&pre_image).try_into().unwrap() -} - -fn tcb_id_string_to_u8(tcb_id: &str) -> u8 { - match tcb_id { - "SGX" => 0, - "TDX" => 1, - _ => panic!("Unknown TCB_ID"), - } -} diff --git a/src/utils/tdx_module.rs b/src/utils/tdx_module.rs deleted file mode 100644 index a0e62ae..0000000 --- a/src/utils/tdx_module.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::types::tcbinfo::TcbInfoV3; -use crate::types::TcbStatus; - -// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/7e5b2a13ca5472de8d97dd7d7024c2ea5af9a6ba/Src/AttestationLibrary/src/Verifiers/Checks/TdxModuleCheck.cpp#L62-L97 -pub fn get_tdx_module_identity_and_tcb( - tee_tcb_svn: &[u8; 16], - tcb_info_v3: &TcbInfoV3, -) -> (TcbStatus, [u8; 48], u64) { - let tdx_module = if let Some(tdx_module_obj) = &tcb_info_v3.tcb_info.tdx_module { - tdx_module_obj - } else { - panic!("TDX module not found"); - }; - - let tdx_module_isv_svn = tee_tcb_svn[0]; - let tdx_module_version = tee_tcb_svn[1]; - - if tdx_module_version == 0 { - let mut mrsigner: [u8; 48] = [0; 48]; - mrsigner.copy_from_slice(&hex::decode(&tdx_module.mrsigner).unwrap()); - - return ( - TcbStatus::OK, - mrsigner, - from_str_to_u64(tdx_module.attributes.as_str()), - ); - } - - let tdx_module_identity_id = format!("TDX_{:02x}", tdx_module_version); - if let Some(tdx_module_identities) = &tcb_info_v3.tcb_info.tdx_module_identities { - for tdx_module_identity in tdx_module_identities.iter() { - if tdx_module_identity.id == tdx_module_identity_id { - for tcb_level in &tdx_module_identity.tcb_levels { - if tdx_module_isv_svn >= tcb_level.tcb.isvsvn { - let mut mrsigner: [u8; 48] = [0; 48]; - mrsigner - .copy_from_slice(&hex::decode(&tdx_module_identity.mrsigner).unwrap()); - let attributes = &tdx_module_identity.attributes; - let tcb_status = TcbStatus::from_str(tcb_level.tcb_status.as_str()); - return (tcb_status, mrsigner, from_str_to_u64(attributes.as_str())); - } - } - } - } - } else { - panic!("TDX module identities not found"); - } - - panic!("TDX Module could not match to any TCB Level for TSX Module ISVSN: {}", tdx_module_isv_svn); -} - -// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/7e5b2a13ca5472de8d97dd7d7024c2ea5af9a6ba/Src/AttestationLibrary/src/Verifiers/Checks/TdxModuleCheck.cpp#L99-L137 -pub fn converge_tcb_status_with_tdx_module_tcb( - tcb_status: TcbStatus, - tdx_module_tcb_status: TcbStatus, -) -> TcbStatus { - let converged_tcb_status: TcbStatus; - match tdx_module_tcb_status { - TcbStatus::TcbOutOfDate => { - if tcb_status == TcbStatus::OK || tcb_status == TcbStatus::TcbSwHardeningNeeded { - converged_tcb_status = TcbStatus::TcbOutOfDate; - } else if tcb_status == TcbStatus::TcbConfigurationNeeded - || tcb_status == TcbStatus::TcbConfigurationAndSwHardeningNeeded - { - converged_tcb_status = TcbStatus::TcbOutOfDateConfigurationNeeded; - } else { - converged_tcb_status = tcb_status; - } - }, - _ => { - converged_tcb_status = tcb_status; - } - } - converged_tcb_status -} - -fn from_str_to_u64(str: &str) -> u64 { - assert!(str.len() == 16, "invalid u64 str length"); - - match u64::from_str_radix(str, 16) { - Ok(ret) => ret, - Err(_) => panic!("Invalid hex character found"), - } -} From ba997a90b67410699ff850df66e0a8726f5dce70 Mon Sep 17 00:00:00 2001 From: Udit Samani Date: Tue, 22 Apr 2025 06:33:16 +0100 Subject: [PATCH 02/14] feat: add from_bytes support for TcbInfo (#17) --- src/types/tcb_info.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 23bf0e9..6159181 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -223,6 +223,11 @@ impl TcbInfo { _ => platform_status, } } + + pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + borsh::from_slice::(bytes) + .map_err(|e| anyhow::anyhow!("Failed to parse TcbInfo: {}", e)) + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] From 5ca5351474fe6fa2f6d1a3859f3f24d9a7540f2a Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Sun, 4 May 2025 16:37:50 +0800 Subject: [PATCH 03/14] expose json fields --- src/types/enclave_identity.rs | 4 ++-- src/types/tcb_info.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index 26a7689..113d97e 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -20,9 +20,9 @@ pub struct QeTcb { #[derive(Deserialize, Serialize, Debug)] pub struct QuotingEnclaveIdentityAndSignature { #[serde(rename = "enclaveIdentity")] - enclave_identity_raw: Box, + pub enclave_identity_raw: Box, #[serde(with = "hex")] - signature: Vec, + pub signature: Vec, } impl QuotingEnclaveIdentityAndSignature { diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 6159181..3117b1e 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -14,9 +14,9 @@ use super::{quote::{Quote, QuoteBody}, report::Td10ReportBody, sgx_x509::SgxPckE #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TcbInfoAndSignature { #[serde(rename = "tcbInfo")] - tcb_info_raw: Box, + pub tcb_info_raw: Box, #[serde(with = "hex")] - signature: Vec, + pub signature: Vec, } impl TryFrom for TcbInfoAndSignature { From a9f23687e71e0ea6476e1aec74ac602843a58215 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Mon, 5 May 2025 08:49:06 +0800 Subject: [PATCH 04/14] borsh methods for enclave_identity and tcb_info --- src/types/enclave_identity.rs | 10 ++++++++++ src/types/tcb_info.rs | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index 113d97e..fdd3cc3 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -129,6 +129,16 @@ impl EnclaveIdentity { .map(|level| level.tcb_status.clone()) .unwrap_or(QeTcbStatus::Unspecified) } + + pub fn from_borsh_bytes(bytes: &[u8]) -> anyhow::Result { + borsh::from_slice::(bytes) + .map_err(|e| anyhow::anyhow!("Failed to deserialize enclave identity: {}", e)) + } + + pub fn to_borsh_bytes(&self) -> anyhow::Result> { + borsh::to_vec(self) + .map_err(|e| anyhow::anyhow!("Failed to serialize enclave identity: {}", e)) + } } /// Enclave TCB level diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 3117b1e..8d4988a 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -224,10 +224,15 @@ impl TcbInfo { } } - pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { + pub fn from_borsh_bytes(bytes: &[u8]) -> anyhow::Result { borsh::from_slice::(bytes) .map_err(|e| anyhow::anyhow!("Failed to parse TcbInfo: {}", e)) } + + pub fn to_borsh_bytes(&self) -> anyhow::Result> { + borsh::to_vec(self) + .map_err(|e| anyhow::anyhow!("Failed to serialize TcbInfo: {}", e)) + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] From 12f087f5ccb2f34135b20f6eec2c04fdba164625 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Mon, 5 May 2025 10:40:09 +0800 Subject: [PATCH 05/14] expose more fields --- src/types/enclave_identity.rs | 2 +- src/types/tcb_info.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index fdd3cc3..1a4a4e5 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -90,7 +90,7 @@ pub struct EnclaveIdentity { /// flavours of SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE Identity. /// This sequence number allows users to easily determine when a particular TCB Info/QE Identity/QVE Identity /// superseedes another TCB Info/QE Identity/QVE Identity (value: current TCB Recovery event number stored in the database). - _tcb_evaluation_data_number: u16, + pub tcb_evaluation_data_number: u16, /// Base 16-encoded string representing miscselect "golden" value (upon applying mask). #[serde(with = "u32_hex")] diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 8d4988a..33537bd 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -80,7 +80,7 @@ impl TcbInfoAndSignature { #[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] #[serde(try_from = "u16")] #[borsh(use_discriminant = true)] -pub(crate) enum TcbInfoVersion { +pub enum TcbInfoVersion { V2 = 2, V3 = 3, } @@ -100,8 +100,8 @@ impl TryFrom for TcbInfoVersion { #[serde(rename_all = "camelCase")] pub struct TcbInfo { #[serde(skip_serializing_if = "Option::is_none", rename = "id")] - id: Option, - version: TcbInfoVersion, + pub id: Option, + pub version: TcbInfoVersion, #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] pub issue_date: chrono::DateTime, #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] @@ -110,13 +110,13 @@ pub struct TcbInfo { pub fmspc: [u8; 6], #[serde(with = "hex")] pub pce_id: [u8; 2], - tcb_type: u16, - _tcb_evaluation_data_number: u16, + pub tcb_type: u16, + pub tcb_evaluation_data_number: u16, #[serde(skip_serializing_if = "Option::is_none")] - tdx_module: Option, + pub tdx_module: Option, #[serde(skip_serializing_if = "Option::is_none")] - tdx_module_identities: Option>, - tcb_levels: Vec, + pub tdx_module_identities: Option>, + pub tcb_levels: Vec, } impl TcbInfo { From f386a5d2ccd1d2e468e6353335716ebacebf3d3a Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Mon, 5 May 2025 10:43:16 +0800 Subject: [PATCH 06/14] derived Eq and PartialEq for enclave_identity types --- src/types/enclave_identity.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index 1a4a4e5..97742fc 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -11,7 +11,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; const ENCLAVE_IDENTITY_V2: u16 = 2; -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] #[serde(rename_all = "camelCase")] pub struct QeTcb { pub isvsvn: u16, @@ -66,7 +66,7 @@ impl QuotingEnclaveIdentityAndSignature { } } -#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize)] +#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct EnclaveIdentity { /// Identifier of the SGX Enclave issued by Intel. @@ -142,7 +142,7 @@ impl EnclaveIdentity { } /// Enclave TCB level -#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize)] +#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct QeTcbLevel { /// SGX Enclave's ISV SVN @@ -158,7 +158,7 @@ pub struct QeTcbLevel { } /// TCB level status -#[derive(Deserialize, Serialize, Debug, Clone, BorshDeserialize, BorshSerialize)] +#[derive(Deserialize, Serialize, Debug, Clone, BorshDeserialize, BorshSerialize, Eq, PartialEq)] pub enum QeTcbStatus { /// TCB level of the SGX platform is up-to-date. UpToDate, @@ -224,7 +224,7 @@ impl std::str::FromStr for QeTcbStatus { } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] #[serde(rename_all = "UPPERCASE")] pub enum EnclaveType { /// Quoting Enclave From e3df051364b93b81c4080f7ec45e319eb92b62b7 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Wed, 25 Jun 2025 16:47:46 +0800 Subject: [PATCH 07/14] JSON collaterals serialization with Bytemuck, and implementation of their corresponding ZeroCopy types (#19) * expose TcbInfoAndSignature fields, serialize version to u16 * fmspc and pceid deserialize as strings * fixed fmspc and pceid pck comparison * more bytes should be deserialized as string types * dont use map for getters so we dont depend on copy trait * change tcbv3components ordering * removed borsh and use bytemuck instead * removed compute digest * first attempt at bytemuck serialization * bytemuck tcb serialization passed testing * renamed tcb_info pod module * zero-copy independant of rust native tcb_info * zero-copy feature * i64 for timestamps * incorrect tdx component lookup * implemented zero copy tcb lookup * features set update * pod tcb_info utils should not depend on TcbStatus * comments about space and alignment for pod types * reorganize pod modules * qe identity type update: parse hexstrings as strings * enclave identity bytemuck serialization * implemented getters to return hex string data as bytes array * modified tcb status enum ordering * change tee type byte order in verified output * fixed converge_tcb_status_with_tdx_module --- Cargo.toml | 7 +- data/tcb_info_v2.json | 222 +------- data/tcb_info_v3_sgx.json | 1 + src/lib.rs | 64 ++- src/types/enclave_identity.rs | 221 +++---- src/types/mod.rs | 9 +- src/types/pod/enclave_identity/mod.rs | 109 ++++ src/types/pod/enclave_identity/serialize.rs | 193 +++++++ src/types/pod/enclave_identity/tests.rs | 140 +++++ .../enclave_identity/zero_copy/conversion.rs | 102 ++++ .../pod/enclave_identity/zero_copy/error.rs | 34 ++ .../enclave_identity/zero_copy/iterators.rs | 172 ++++++ .../pod/enclave_identity/zero_copy/mod.rs | 17 + .../pod/enclave_identity/zero_copy/structs.rs | 134 +++++ src/types/pod/mod.rs | 2 + src/types/pod/tcb_info/mod.rs | 130 +++++ src/types/pod/tcb_info/serialize.rs | 326 +++++++++++ src/types/pod/tcb_info/tests.rs | 539 ++++++++++++++++++ .../pod/tcb_info/zero_copy/conversion.rs | 211 +++++++ src/types/pod/tcb_info/zero_copy/error.rs | 52 ++ src/types/pod/tcb_info/zero_copy/iterators.rs | 367 ++++++++++++ src/types/pod/tcb_info/zero_copy/mod.rs | 22 + src/types/pod/tcb_info/zero_copy/structs.rs | 323 +++++++++++ src/types/pod/tcb_info/zero_copy/utils/mod.rs | 162 ++++++ src/types/tcb_info.rs | 374 ++++++------ src/utils.rs | 55 +- 26 files changed, 3392 insertions(+), 596 deletions(-) create mode 100644 data/tcb_info_v3_sgx.json create mode 100644 src/types/pod/enclave_identity/mod.rs create mode 100644 src/types/pod/enclave_identity/serialize.rs create mode 100644 src/types/pod/enclave_identity/tests.rs create mode 100644 src/types/pod/enclave_identity/zero_copy/conversion.rs create mode 100644 src/types/pod/enclave_identity/zero_copy/error.rs create mode 100644 src/types/pod/enclave_identity/zero_copy/iterators.rs create mode 100644 src/types/pod/enclave_identity/zero_copy/mod.rs create mode 100644 src/types/pod/enclave_identity/zero_copy/structs.rs create mode 100644 src/types/pod/mod.rs create mode 100644 src/types/pod/tcb_info/mod.rs create mode 100644 src/types/pod/tcb_info/serialize.rs create mode 100644 src/types/pod/tcb_info/tests.rs create mode 100644 src/types/pod/tcb_info/zero_copy/conversion.rs create mode 100644 src/types/pod/tcb_info/zero_copy/error.rs create mode 100644 src/types/pod/tcb_info/zero_copy/iterators.rs create mode 100644 src/types/pod/tcb_info/zero_copy/mod.rs create mode 100644 src/types/pod/tcb_info/zero_copy/structs.rs create mode 100644 src/types/pod/tcb_info/zero_copy/utils/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 257ecb9..aee28e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ base64ct = { version = "=1.6.0" } p256 = { version = "0.13.2" } sha2 = { version = "0.10.8" } sha3 = { version = "0.10.8" } -borsh = { version = "1.5.7", features = ["derive"]} +bytemuck = { version = "1.23.0", features = ["derive"] } [dependencies.x509-verify] version = "0.4.6" @@ -36,4 +36,7 @@ default-features = false features = [ "std", "p256", "x509" ] [features] -default = [] +default = ["full"] +# Solana BPF must use the "zero-copy" feature +zero-copy = [] +full = [] diff --git a/data/tcb_info_v2.json b/data/tcb_info_v2.json index 663a379..e5d6a22 100644 --- a/data/tcb_info_v2.json +++ b/data/tcb_info_v2.json @@ -1,221 +1 @@ -{ - "tcbInfo": { - "version": 2, - "issueDate": "2024-09-10T06:26:58Z", - "nextUpdate": "2024-10-10T06:26:58Z", - "fmspc": "00606a000000", - "pceId": "0000", - "tcbType": 0, - "tcbEvaluationDataNumber": 16, - "tcbLevels": [ - { - "tcb": { - "sgxtcbcomp01svn": 12, - "sgxtcbcomp02svn": 12, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 1, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2023-08-09T00:00:00Z", - "tcbStatus": "SWHardeningNeeded" - }, - { - "tcb": { - "sgxtcbcomp01svn": 12, - "sgxtcbcomp02svn": 12, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2023-08-09T00:00:00Z", - "tcbStatus": "ConfigurationAndSWHardeningNeeded" - }, - { - "tcb": { - "sgxtcbcomp01svn": 11, - "sgxtcbcomp02svn": 11, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 1, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2023-02-15T00:00:00Z", - "tcbStatus": "OutOfDate" - }, - { - "tcb": { - "sgxtcbcomp01svn": 11, - "sgxtcbcomp02svn": 11, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2023-02-15T00:00:00Z", - "tcbStatus": "OutOfDateConfigurationNeeded" - }, - { - "tcb": { - "sgxtcbcomp01svn": 7, - "sgxtcbcomp02svn": 9, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 1, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2022-08-10T00:00:00Z", - "tcbStatus": "OutOfDate" - }, - { - "tcb": { - "sgxtcbcomp01svn": 7, - "sgxtcbcomp02svn": 9, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 13 - }, - "tcbDate": "2022-08-10T00:00:00Z", - "tcbStatus": "OutOfDateConfigurationNeeded" - }, - { - "tcb": { - "sgxtcbcomp01svn": 4, - "sgxtcbcomp02svn": 4, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 11 - }, - "tcbDate": "2021-11-10T00:00:00Z", - "tcbStatus": "OutOfDate" - }, - { - "tcb": { - "sgxtcbcomp01svn": 4, - "sgxtcbcomp02svn": 4, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 10 - }, - "tcbDate": "2020-11-11T00:00:00Z", - "tcbStatus": "OutOfDate" - }, - { - "tcb": { - "sgxtcbcomp01svn": 4, - "sgxtcbcomp02svn": 4, - "sgxtcbcomp03svn": 3, - "sgxtcbcomp04svn": 3, - "sgxtcbcomp05svn": 255, - "sgxtcbcomp06svn": 255, - "sgxtcbcomp07svn": 0, - "sgxtcbcomp08svn": 0, - "sgxtcbcomp09svn": 0, - "sgxtcbcomp10svn": 0, - "sgxtcbcomp11svn": 0, - "sgxtcbcomp12svn": 0, - "sgxtcbcomp13svn": 0, - "sgxtcbcomp14svn": 0, - "sgxtcbcomp15svn": 0, - "sgxtcbcomp16svn": 0, - "pcesvn": 5 - }, - "tcbDate": "2018-01-04T00:00:00Z", - "tcbStatus": "OutOfDate" - } - ] - }, - "signature": "66902eb508294eea2e1c46b04b66c3c6db90498f8116ac3b3630562e917f7dc513800203f2e7572bdade7744b64b30c72006c96b500242159331329e0999f031" -} +{"tcbInfo":{"version":2,"issueDate":"2025-05-12T13:43:39Z","nextUpdate":"2025-06-11T13:43:39Z","fmspc":"B0C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":17,"tcbLevels":[{"tcb":{"sgxtcbcomp01svn":2,"sgxtcbcomp02svn":2,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":2,"sgxtcbcomp05svn":3,"sgxtcbcomp06svn":1,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":3,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":11},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomp01svn":2,"sgxtcbcomp02svn":2,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":2,"sgxtcbcomp05svn":3,"sgxtcbcomp06svn":1,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":3,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":5},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate"}]},"signature":"8d15a257764b90a68ab6dc10fa14715d5143e2da6bb72c8910cab5320ffbebd160d6e0a0777b54c48905ff7c733796a031b28237a12be5f91ce32f37373dc74b"} \ No newline at end of file diff --git a/data/tcb_info_v3_sgx.json b/data/tcb_info_v3_sgx.json new file mode 100644 index 0000000..4fdb80f --- /dev/null +++ b/data/tcb_info_v3_sgx.json @@ -0,0 +1 @@ +{"tcbInfo":{"id":"SGX","version":3,"issueDate":"2025-05-13T02:04:31Z","nextUpdate":"2025-06-12T02:04:31Z","fmspc":"B0C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":17,"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00837"]}]},"signature":"0b14e5a35d17beda3ab777d97a9db472b747c811091eac06784f40cd41886ad8eb76647721ce54e32eec5af87d9a7faeea28e5adf6b16582bd70683960bdcc63"} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 0325d69..c26a63d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,17 @@ pub mod trust_store; pub mod types; pub mod utils; -use std::time::SystemTime; - +#[cfg(feature = "full")] use anyhow::{Context, anyhow, bail}; +#[cfg(feature = "full")] use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; +#[cfg(feature = "full")] +use std::time::SystemTime; +#[cfg(feature = "full")] use trust_store::{TrustStore, TrustedIdentity}; +#[cfg(feature = "full")] use types::{ VerifiedOutput, collateral::Collateral, @@ -16,9 +21,13 @@ use types::{ sgx_x509::SgxPckExtension, tcb_info::{TcbInfo, TcbStatus}, }; +#[cfg(feature = "full")] use utils::Expireable; +#[cfg(feature = "full")] use x509_cert::der::{Any, DecodePem}; +#[cfg(feature = "full")] use x509_verify::VerifyingKey as X509VerifyingKey; +#[cfg(feature = "full")] use zerocopy::AsBytes; pub const INTEL_ROOT_CA_PEM: &str = "\ @@ -27,6 +36,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi71OiO SLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlA== -----END PUBLIC KEY-----"; +#[cfg(feature = "full")] pub fn verify_dcap_quote( current_time: SystemTime, collateral: Collateral, @@ -41,7 +51,8 @@ pub fn verify_dcap_quote( // 3. Verify the status of Intel SGX TCB described in the chain. let pck_extension = quote.signature.get_pck_extension()?; - let (sgx_tcb_status, tdx_tcb_status, advisory_ids) = verify_tcb_status(&tcb_info, &pck_extension, "e)?; + let (sgx_tcb_status, tdx_tcb_status, advisory_ids) = + verify_tcb_status(&tcb_info, &pck_extension, "e)?; assert!( sgx_tcb_status != TcbStatus::Revoked || tdx_tcb_status != TcbStatus::Revoked, @@ -70,16 +81,15 @@ pub fn verify_dcap_quote( Ok(VerifiedOutput { quote_version: quote.header.version.get(), - tee_type: quote.header.tee_type, - tcb_status, + tee_type: quote.header.tee_type.to_le(), // Compatible with VerifiedOutput defined on-chain + tcb_status: tcb_status as u8, fmspc: pck_extension.fmspc, quote_body: quote.body, advisory_ids, }) } - - +#[cfg(feature = "full")] fn verify_integrity( current_time: SystemTime, collateral: &Collateral, @@ -181,6 +191,7 @@ fn verify_integrity( Ok(tcb_info) } +#[cfg(feature = "full")] fn verify_quote( current_time: SystemTime, collateral: &Collateral, @@ -193,12 +204,12 @@ fn verify_quote( /// Verify the quote enclave source and return the TCB status /// of the quoting enclave. +#[cfg(feature = "full")] pub fn verify_quote_enclave_source( current_time: SystemTime, collateral: &Collateral, quote: &Quote, ) -> anyhow::Result { - // Verify that the enclave identity root is signed by root certificate let qe_identity = collateral .qe_identity @@ -222,10 +233,11 @@ pub fn verify_quote_enclave_source( } // Compare the mr_signer values - if qe_identity.mrsigner != quote.signature.qe_report_body.mr_signer { + let qe_identity_mr_signer_bytes: [u8; 32] = qe_identity.mrsigner_bytes(); + if qe_identity_mr_signer_bytes != quote.signature.qe_report_body.mr_signer { bail!( "invalid qe mrsigner, expected {} but got {}", - hex::encode(qe_identity.mrsigner), + qe_identity.mrsigner.as_str(), hex::encode(quote.signature.qe_report_body.mr_signer) ); } @@ -241,14 +253,14 @@ pub fn verify_quote_enclave_source( // Compare the attribute values let qe_report_attributes = quote.signature.qe_report_body.sgx_attributes; - let calculated_mask = qe_identity - .attributes_mask + let qe_identity_attributes_bytes: [u8; 16] = qe_identity.attributes_bytes(); + let calculated_mask = qe_identity_attributes_bytes .iter() .zip(qe_report_attributes.iter()) .map(|(&mask, &attribute)| mask & attribute); if calculated_mask - .zip(qe_identity.attributes) + .zip(qe_identity_attributes_bytes) .any(|(masked, identity)| masked != identity) { bail!("qe attrtibutes mismatch"); @@ -256,15 +268,14 @@ pub fn verify_quote_enclave_source( // Compare misc_select values let misc_select = quote.signature.qe_report_body.misc_select; - let calculated_mask = qe_identity - .miscselect_mask - .as_bytes() + let qe_identity_misc_select_bytes: [u8; 4] = qe_identity.miscselect_bytes(); + let calculated_mask = qe_identity_misc_select_bytes .iter() .zip(misc_select.as_bytes().iter()) .map(|(&mask, &attribute)| mask & attribute); if calculated_mask - .zip(qe_identity.miscselect.as_bytes().iter()) + .zip(qe_identity_misc_select_bytes.iter()) .any(|(masked, &identity)| masked != identity) { bail!("qe misc_select mismatch"); @@ -276,6 +287,7 @@ pub fn verify_quote_enclave_source( } /// Verify the quote signatures. +#[cfg(feature = "full")] pub fn verify_quote_signatures(quote: &Quote) -> anyhow::Result<()> { let pck_cert_chain_data = quote.signature.get_pck_cert_chain()?; let pck_pk_bytes = pck_cert_chain_data.pck_cert_chain[0] @@ -325,13 +337,19 @@ pub fn verify_quote_signatures(quote: &Quote) -> anyhow::Result<()> { /// Ensure the latest tcb info is not revoked, and is either up to date or only needs a configuration /// change. +#[cfg(feature = "full")] pub fn verify_tcb_status( tcb_info: &TcbInfo, pck_extension: &SgxPckExtension, quote: &Quote, ) -> anyhow::Result<(TcbStatus, TcbStatus, Vec)> { // Make sure the tcb_info matches the enclave's model/PCE version - if pck_extension.fmspc != tcb_info.fmspc { + + let tcb_info_fmspc_bytes: [u8; 6] = tcb_info.fmspc_bytes(); + + let tcb_info_pce_id_bytes: [u8; 2] = tcb_info.pce_id_bytes(); + + if pck_extension.fmspc != tcb_info_fmspc_bytes { return Err(anyhow::anyhow!( "tcb fmspc mismatch (pck extension: {:?}, tcb_info: {:?})", pck_extension.fmspc, @@ -339,7 +357,7 @@ pub fn verify_tcb_status( )); } - if pck_extension.pceid != tcb_info.pce_id { + if pck_extension.pceid != tcb_info_pce_id_bytes { return Err(anyhow::anyhow!( "tcb pceid mismatch (pck extension: {:?}, tcb_info: {:?})", pck_extension.pceid, @@ -350,18 +368,16 @@ pub fn verify_tcb_status( TcbStatus::lookup(pck_extension, tcb_info, quote) } -#[cfg(test)] +#[cfg(all(test, not(feature = "zero-copy")))] mod tests { use std::time::Duration; use x509_cert::{crl::CertificateList, der::Decode}; + use crate::types::tcb_info::TcbInfoAndSignature; use crate::{ - types::{ - enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature, - }, - utils::cert_chain_processor, + types::enclave_identity::QuotingEnclaveIdentityAndSignature, utils::cert_chain_processor, }; use super::*; diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index 97742fc..ed8777e 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -1,17 +1,13 @@ -use super::{UInt32LE, tcb_info::TcbStatus}; -use crate::utils::u32_hex; -use crate::utils::borsh_datetime_as_instant; -use crate::utils::borsh_uint32le; use anyhow::Context; use chrono::Utc; use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; -use borsh::{BorshDeserialize, BorshSerialize}; +use super::tcb_info::TcbStatus; -const ENCLAVE_IDENTITY_V2: u16 = 2; +const ENCLAVE_IDENTITY_V2: u32 = 2; -#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct QeTcb { pub isvsvn: u16, @@ -66,23 +62,23 @@ impl QuotingEnclaveIdentityAndSignature { } } -#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct EnclaveIdentity { /// Identifier of the SGX Enclave issued by Intel. pub id: EnclaveType, /// Version of the structure. - pub version: u16, + pub version: u32, /// The time the Enclave Identity Information was created. The time shalle be in UTC /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub issue_date: chrono::DateTime, /// The time by which next Enclave Identity information will be issued. The time shall be in UTC /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub next_update: chrono::DateTime, /// A monotonically increasing sequence number changed when Intel updates the content of the TCB evaluation data set: @@ -93,26 +89,19 @@ pub struct EnclaveIdentity { pub tcb_evaluation_data_number: u16, /// Base 16-encoded string representing miscselect "golden" value (upon applying mask). - #[serde(with = "u32_hex")] - #[borsh(deserialize_with = "borsh_uint32le::deserialize", serialize_with = "borsh_uint32le::serialize")] - pub miscselect: UInt32LE, + pub miscselect: String, /// Base 16-encoded string representing mask to be applied to miscselect value retrieved from the platform. - #[serde(with = "u32_hex")] - #[borsh(deserialize_with = "borsh_uint32le::deserialize", serialize_with = "borsh_uint32le::serialize")] - pub miscselect_mask: UInt32LE, + pub miscselect_mask: String, /// Base 16-encoded string representing attributes "golden" value (upon applying mask). - #[serde(with = "hex")] - pub attributes: [u8; 16], + pub attributes: String, /// Base 16-encoded string representing mask to be applied to attributes value retrieved from the platform. - #[serde(with = "hex")] - pub attributes_mask: [u8; 16], + pub attributes_mask: String, /// Base 16-encoded string representing mrsigner hash. - #[serde(with = "hex")] - pub mrsigner: [u8; 32], + pub mrsigner: String, /// Enclave Product ID. pub isvprodid: u16, @@ -122,6 +111,41 @@ pub struct EnclaveIdentity { } impl EnclaveIdentity { + pub fn miscselect_bytes(&self) -> [u8; 4] { + hex::decode(&self.miscselect) + .expect("Failed to decode miscselect") + .try_into() + .expect("miscselect should be 4 bytes") + } + + pub fn miscselect_mask_bytes(&self) -> [u8; 4] { + hex::decode(&self.miscselect_mask) + .expect("Failed to decode miscselect mask") + .try_into() + .expect("miscselect mask should be 4 bytes") + } + + pub fn attributes_bytes(&self) -> [u8; 16] { + hex::decode(&self.attributes) + .expect("Failed to decode attributes") + .try_into() + .expect("attributes should be 16 bytes") + } + + pub fn attributes_mask_bytes(&self) -> [u8; 16] { + hex::decode(&self.attributes_mask) + .expect("Failed to decode attributes mask") + .try_into() + .expect("attributes mask should be 16 bytes") + } + + pub fn mrsigner_bytes(&self) -> [u8; 32] { + hex::decode(&self.mrsigner) + .expect("Failed to decode mrsigner") + .try_into() + .expect("mrsigner should be 32 bytes") + } + pub fn get_qe_tcb_status(&self, isv_svn: u16) -> QeTcbStatus { self.tcb_levels .iter() @@ -129,52 +153,35 @@ impl EnclaveIdentity { .map(|level| level.tcb_status.clone()) .unwrap_or(QeTcbStatus::Unspecified) } - - pub fn from_borsh_bytes(bytes: &[u8]) -> anyhow::Result { - borsh::from_slice::(bytes) - .map_err(|e| anyhow::anyhow!("Failed to deserialize enclave identity: {}", e)) - } - - pub fn to_borsh_bytes(&self) -> anyhow::Result> { - borsh::to_vec(self) - .map_err(|e| anyhow::anyhow!("Failed to serialize enclave identity: {}", e)) - } } /// Enclave TCB level -#[derive(Deserialize, Serialize, Debug, BorshDeserialize, BorshSerialize, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct QeTcbLevel { /// SGX Enclave's ISV SVN - tcb: QeTcb, + pub tcb: QeTcb, /// The time the TCB was evaluated. The time shall be in UTC and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] - _tcb_date: chrono::DateTime, + + pub tcb_date: chrono::DateTime, /// TCB level status - tcb_status: QeTcbStatus, + pub tcb_status: QeTcbStatus, #[serde(rename = "advisoryIDs")] #[serde(skip_serializing_if = "Option::is_none")] pub advisory_ids: Option>, } /// TCB level status -#[derive(Deserialize, Serialize, Debug, Clone, BorshDeserialize, BorshSerialize, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +#[repr(u8)] pub enum QeTcbStatus { - /// TCB level of the SGX platform is up-to-date. UpToDate, - /// TCB level of SGX platform requires SW hardening. SWHardeningNeeded, - /// TCB level of SGX platform is outdated. + ConfigurationAndSWHardeningNeeded, + ConfigurationNeeded, OutOfDate, - /// TCB level of SGX platform is outdated and requires a configuration change. OutOfDateConfigurationNeeded, - /// TCB level of SGX platform is outdated and requires a configuration change. - ConfigurationNeeded, - /// TCB level of SGX platform is outdated and requires a configuration change and SW hardening. - ConfigurationAndSWHardeningNeeded, - /// TCB level of SGX platform is revoked. Revoked, - /// Unknown TCB level status. Unspecified, } @@ -193,6 +200,38 @@ impl std::fmt::Display for QeTcbStatus { } } +impl TryFrom for QeTcbStatus { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(QeTcbStatus::UpToDate), + 1 => Ok(QeTcbStatus::SWHardeningNeeded), + 2 => Ok(QeTcbStatus::OutOfDate), + 3 => Ok(QeTcbStatus::OutOfDateConfigurationNeeded), + 4 => Ok(QeTcbStatus::ConfigurationNeeded), + 5 => Ok(QeTcbStatus::ConfigurationAndSWHardeningNeeded), + 6 => Ok(QeTcbStatus::Revoked), + _ => Err("Invalid TCB status"), + } + } +} + +impl From for u8 { + fn from(value: QeTcbStatus) -> Self { + match value { + QeTcbStatus::UpToDate => 0, + QeTcbStatus::SWHardeningNeeded => 1, + QeTcbStatus::OutOfDate => 2, + QeTcbStatus::OutOfDateConfigurationNeeded => 3, + QeTcbStatus::ConfigurationNeeded => 4, + QeTcbStatus::ConfigurationAndSWHardeningNeeded => 5, + QeTcbStatus::Revoked => 6, + QeTcbStatus::Unspecified => 7, + } + } +} + #[allow(clippy::from_over_into)] impl Into for QeTcbStatus { fn into(self) -> TcbStatus { @@ -224,74 +263,58 @@ impl std::str::FromStr for QeTcbStatus { } } -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "UPPERCASE")] +#[repr(u8)] pub enum EnclaveType { /// Quoting Enclave - Qe, + Qe = 0, /// Quote Verification Enclave - Qve, + Qve = 1, /// TDX Quoting Enclave #[serde(rename = "TD_QE")] - TdQe, + TdQe = 2, +} + +impl TryFrom for EnclaveType { + type Error = &'static str; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(EnclaveType::Qe), + 1 => Ok(EnclaveType::Qve), + 2 => Ok(EnclaveType::TdQe), + _ => Err("Invalid enclave type"), + } + } } +impl From for u8 { + fn from(enclave_type: EnclaveType) -> Self { + match enclave_type { + EnclaveType::Qe => 0, + EnclaveType::Qve => 1, + EnclaveType::TdQe => 2, + } + } +} #[cfg(test)] mod tests { use super::*; + use sha2::{Digest, Sha256}; #[test] fn test_enclave_identity_serialization() { let qe_identity = include_bytes!("../../data/qeidentityv2_apiv4.json"); let qe_identity: QuotingEnclaveIdentityAndSignature = serde_json::from_slice(qe_identity).unwrap(); - let original_qe_identity = qe_identity.get_enclave_identity().unwrap(); - - let enclave_identity_bytes = borsh::to_vec(&original_qe_identity).unwrap(); - let deserialized_qe_identity = EnclaveIdentity::try_from_slice(&enclave_identity_bytes).unwrap(); - - // Verify that the deserialized enclave identity matches the original - assert_eq!(original_qe_identity.id, deserialized_qe_identity.id); - assert_eq!(original_qe_identity.version, deserialized_qe_identity.version); - assert_eq!(original_qe_identity.issue_date, deserialized_qe_identity.issue_date); - assert_eq!(original_qe_identity.next_update, deserialized_qe_identity.next_update); - assert_eq!(original_qe_identity.miscselect, deserialized_qe_identity.miscselect); - assert_eq!(original_qe_identity.miscselect_mask, deserialized_qe_identity.miscselect_mask); - assert_eq!(original_qe_identity.attributes, deserialized_qe_identity.attributes); - assert_eq!(original_qe_identity.attributes_mask, deserialized_qe_identity.attributes_mask); - assert_eq!(original_qe_identity.mrsigner, deserialized_qe_identity.mrsigner); - assert_eq!(original_qe_identity.isvprodid, deserialized_qe_identity.isvprodid); - assert_eq!(original_qe_identity.tcb_levels.len(), deserialized_qe_identity.tcb_levels.len()); - - // Detailed verification of each tcb_level - for (i, original_tcb_level) in original_qe_identity.tcb_levels.iter().enumerate() { - let deserialized_tcb_level = &deserialized_qe_identity.tcb_levels[i]; - - // Verify TCB values - assert_eq!(original_tcb_level.tcb.isvsvn, deserialized_tcb_level.tcb.isvsvn); - - // Verify TCB date - assert_eq!(original_tcb_level._tcb_date, deserialized_tcb_level._tcb_date); - - // Verify TCB status - assert!(matches!( - &original_tcb_level.tcb_status, - status if std::mem::discriminant(status) == std::mem::discriminant(&deserialized_tcb_level.tcb_status) - )); + let qe_identity_parsed = qe_identity.get_enclave_identity().unwrap(); - // Verify advisory IDs - match (&original_tcb_level.advisory_ids, &deserialized_tcb_level.advisory_ids) { - (Some(original_ids), Some(deserialized_ids)) => { - assert_eq!(original_ids.len(), deserialized_ids.len()); - for (j, original_id) in original_ids.iter().enumerate() { - assert_eq!(original_id, &deserialized_ids[j]); - } - }, - (None, None) => {}, - _ => panic!("Advisory IDs mismatch in TCB level {}", i), - } - } - } + let original_qe_identity_hash = Sha256::digest(qe_identity.enclave_identity_raw.get().as_bytes()); + let serialized_qe_identity = serde_json::to_string(&qe_identity_parsed).unwrap(); + let serialized_qe_identity_hash = Sha256::digest(serialized_qe_identity.as_bytes()); + assert_eq!(original_qe_identity_hash, serialized_qe_identity_hash); + } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 89fb773..5c1fdb2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,12 +1,15 @@ use quote::QuoteBody; -use tcb_info::TcbStatus; +#[cfg(feature = "full")] pub mod collateral; +#[cfg(feature = "full")] +pub mod tcb_info; +#[cfg(feature = "full")] pub mod enclave_identity; pub mod quote; pub mod report; pub mod sgx_x509; -pub mod tcb_info; +pub mod pod; pub type UInt16LE = zerocopy::little_endian::U16; pub type UInt32LE = zerocopy::little_endian::U32; @@ -20,7 +23,7 @@ pub type UInt64LE = zerocopy::little_endian::U64; pub struct VerifiedOutput { pub quote_version: u16, pub tee_type: u32, - pub tcb_status: TcbStatus, + pub tcb_status: u8, pub fmspc: [u8; 6], pub quote_body: QuoteBody, pub advisory_ids: Option>, diff --git a/src/types/pod/enclave_identity/mod.rs b/src/types/pod/enclave_identity/mod.rs new file mode 100644 index 0000000..ed12deb --- /dev/null +++ b/src/types/pod/enclave_identity/mod.rs @@ -0,0 +1,109 @@ +use bytemuck::{Pod, Zeroable}; + +pub mod zero_copy; + +// Conditionally import serialization and tests based on features +#[cfg(feature = "full")] +pub mod serialize; + +#[cfg(all(test, not(feature = "zero-copy")))] +mod tests; + +// --- POD Structs for Enclave Identity --- + +/// Header for a Quoting Enclave (QE) TCB Level. +/// The actual advisory ID strings and their lengths array will be in this header's payload. +/// Alignment: 8 bytes (due to i64) +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] // Removed Default, not needed for bytemuck if zeroed +pub struct QeTcbLevelPodHeader { + pub isvsvn: u16, + pub tcb_status: u8, // Represents QeTcbStatus enum + pub _padding0: [u8; 5], // Align to 8 for tcb_date_timestamp + pub tcb_date_timestamp: i64, + pub advisory_ids_count: u32, + pub advisory_ids_lengths_array_len: u32, // Total byte length of the array of u16 lengths + pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings + pub _padding1: [u8; 4], // Ensure struct size is multiple of alignment (8) +} + +/// Main header for Enclave Identity. +/// This is followed by a variable-length payload containing data for tcb_levels. +/// Alignment: 8 bytes (due to i64) +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] // Removed Default +pub struct EnclaveIdentityHeader { + pub id: u8, // Represents EnclaveType enum (e.g., 0: Qe, 1: Qve, 2: TdQe) + pub _padding_id: [u8; 3], // Align to 4 for version + pub version: u32, + pub issue_date_timestamp: i64, + pub next_update_timestamp: i64, + pub tcb_evaluation_data_number: u16, + pub isvprodid: u16, + // Hex strings (fixed-size byte arrays storing UTF-8 hex characters) + pub miscselect_hex: [u8; 8], + pub miscselect_mask_hex: [u8; 8], + pub attributes_hex: [u8; 32], + pub attributes_mask_hex: [u8; 32], + pub mrsigner_hex: [u8; 64], // 64 hex chars for a 32-byte value + + pub tcb_levels_count: u32, + pub tcb_levels_total_payload_len: u32, // Total byte length for all QeTcbLevelPodHeader sections + their payloads + // Ensure total size is a multiple of 8 (max alignment of fields) + // Current size before this padding: + // id (1) + pad_id (3) = 4 + // version (4) = 4 + // issue_date (8) = 8 + // next_update (8) = 8 + // tcb_eval_num (2) + isvprodid (2) = 4 + // miscselect (8) = 8 + // miscselect_mask (8) = 8 + // attributes (32) = 32 + // attributes_mask (32) = 32 + // mrsigner (64) = 64 + // tcb_levels_count (4) = 4 + // tcb_levels_total_payload_len (4) = 4 + // Total = 4+4+8+8+4+8+8+32+32+64+4+4 = 180. Needs to be 184 for multiple of 8. + pub _final_padding: [u8; 4], +} + +/// The top-level POD structure for Enclave Identity and its signature. +/// The variable payload for EnclaveIdentityHeader follows it directly in memory. +/// Alignment: 8 bytes (due to EnclaveIdentityHeader) +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct EnclaveIdentityPod { + pub signature: [u8; 64], // Assuming P256 ECDSA signature + pub enclave_identity_header: EnclaveIdentityHeader, + // The variable payload associated with enclave_identity_header follows here in memory. +} + +// Compile-time assertions for struct sizes and alignments (optional but good practice) +#[cfg(test)] +mod pod_layout_tests { + use super::*; + use core::mem::{size_of, align_of}; + + #[test] + fn check_qe_tcb_level_pod_header_layout() { + assert_eq!(size_of::(), 32); // 2+1+5 + 8 + 4+4+4 + 4 = 32 + assert_eq!(align_of::(), 8); + } + + #[test] + fn check_enclave_identity_header_layout() { + // 1+3 (id) + 4 (version) + 8 (issue) + 8 (next_update) + 2 (tcb_eval) + 2 (isvprodid) + // + 8 (misc) + 8 (misc_mask) + 32 (attr) + 32 (attr_mask) + 64 (mrsigner) + // + 4 (levels_count) + 4 (levels_payload_len) + 4 (final_padding) + // = 4+4+8+8+4+8+8+32+32+64+4+4+4 = 184 + assert_eq!(size_of::(), 184); + assert_eq!(align_of::(), 8); + } + + #[test] + fn check_enclave_identity_pod_layout() { + // 64 (sig) + 184 (header) = 248 + assert_eq!(size_of::(), 248); + assert_eq!(align_of::(), 8); + } +} diff --git a/src/types/pod/enclave_identity/serialize.rs b/src/types/pod/enclave_identity/serialize.rs new file mode 100644 index 0000000..515fde3 --- /dev/null +++ b/src/types/pod/enclave_identity/serialize.rs @@ -0,0 +1,193 @@ +// src/types/pod/enclave_identity/serialize.rs + +#![cfg(feature = "full")] + +use super::zero_copy::{EnclaveIdentityZeroCopy, conversion::enclave_identity_from_zero_copy}; +use super::{EnclaveIdentityHeader, QeTcbLevelPodHeader}; +use crate::types::enclave_identity::{EnclaveIdentity, EnclaveType}; + +use bytemuck::Zeroable; // For zeroed() method +use core::mem; + +// Helper function to append padding (null bytes) to `bytes_vec` so its new total length +// becomes a multiple of `align_to`. +// Returns the number of padding bytes that were added. +fn append_padding_to_align(bytes_vec: &mut Vec, align_to: usize) -> usize { + let current_len = bytes_vec.len(); + let remainder = current_len % align_to; + let mut padding_bytes_added = 0; + if remainder != 0 { + padding_bytes_added = align_to - remainder; + for _ in 0..padding_bytes_added { + bytes_vec.push(0); // Add null bytes for padding + } + } + padding_bytes_added +} + +// Helper function to copy a hex string's ASCII characters to a fixed-size byte array. +// It null-pads if the string is shorter and truncates if longer. +fn hex_chars_to_fixed_bytes(hex_s: &str) -> [u8; N] { + let mut arr = [0u8; N]; + let s_bytes = hex_s.as_bytes(); + let len_to_copy = s_bytes.len().min(N); + arr[..len_to_copy].copy_from_slice(&s_bytes[..len_to_copy]); + arr +} + +// Helper to convert EnclaveType to its u8 representation +fn enclave_type_to_byte(enclave_type: &EnclaveType) -> u8 { + match enclave_type { + EnclaveType::Qe => 0, + EnclaveType::Qve => 1, + EnclaveType::TdQe => 2, + } +} + +// Helper to convert QeTcbStatus to its u8 representation +// This mirrors the mapping in zero_copy/conversion.rs qe_tcb_status_from_byte +fn qe_tcb_status_to_byte(status: &crate::types::enclave_identity::QeTcbStatus) -> u8 { + match status { + crate::types::enclave_identity::QeTcbStatus::UpToDate => 0, + crate::types::enclave_identity::QeTcbStatus::SWHardeningNeeded => 1, + crate::types::enclave_identity::QeTcbStatus::OutOfDate => 2, + crate::types::enclave_identity::QeTcbStatus::OutOfDateConfigurationNeeded => 3, + crate::types::enclave_identity::QeTcbStatus::ConfigurationNeeded => 4, + crate::types::enclave_identity::QeTcbStatus::ConfigurationAndSWHardeningNeeded => 5, + crate::types::enclave_identity::QeTcbStatus::Revoked => 6, + crate::types::enclave_identity::QeTcbStatus::Unspecified => 7, + } +} + +/// Represents the combined Enclave Identity Header and its serialized payload. +pub struct SerializedEnclaveIdentity { + pub header: EnclaveIdentityHeader, + pub payload: Vec, +} + +impl SerializedEnclaveIdentity { + /// Creates a SerializedEnclaveIdentity from an application-level EnclaveIdentity struct. + pub fn from_rust_enclave_identity(rust_ei: &EnclaveIdentity) -> Result { + let mut header = EnclaveIdentityHeader::zeroed(); // Requires Zeroable + let mut payload_bytes = Vec::new(); + let mut current_payload_offset = 0; + + header.id = enclave_type_to_byte(&rust_ei.id); + header.version = rust_ei.version; + header.issue_date_timestamp = rust_ei.issue_date.timestamp(); + header.next_update_timestamp = rust_ei.next_update.timestamp(); + header.tcb_evaluation_data_number = rust_ei.tcb_evaluation_data_number; + header.isvprodid = rust_ei.isvprodid; + + header.miscselect_hex = hex_chars_to_fixed_bytes::<8>(&rust_ei.miscselect); + header.miscselect_mask_hex = hex_chars_to_fixed_bytes::<8>(&rust_ei.miscselect_mask); + header.attributes_hex = hex_chars_to_fixed_bytes::<32>(&rust_ei.attributes); + header.attributes_mask_hex = hex_chars_to_fixed_bytes::<32>(&rust_ei.attributes_mask); + header.mrsigner_hex = hex_chars_to_fixed_bytes::<64>(&rust_ei.mrsigner); + + let tcb_levels_payload_start_offset = current_payload_offset; + header.tcb_levels_count = rust_ei.tcb_levels.len() as u32; + + for rust_tcb_level in &rust_ei.tcb_levels { + let mut qe_tcb_level_header = QeTcbLevelPodHeader::zeroed(); // Requires Zeroable + qe_tcb_level_header.isvsvn = rust_tcb_level.tcb.isvsvn; + qe_tcb_level_header.tcb_status = qe_tcb_status_to_byte(&rust_tcb_level.tcb_status); + qe_tcb_level_header.tcb_date_timestamp = rust_tcb_level.tcb_date.timestamp(); + + let mut advisory_id_lengths_bytes = Vec::new(); + let mut advisory_id_data_bytes = Vec::new(); + + if let Some(advisory_ids) = &rust_tcb_level.advisory_ids { + qe_tcb_level_header.advisory_ids_count = advisory_ids.len() as u32; + for adv_id in advisory_ids { + let adv_id_bytes = adv_id.as_bytes(); + advisory_id_lengths_bytes + .extend_from_slice(&(adv_id_bytes.len() as u16).to_le_bytes()); + advisory_id_data_bytes.extend_from_slice(adv_id_bytes); + } + } else { + qe_tcb_level_header.advisory_ids_count = 0; + } + qe_tcb_level_header.advisory_ids_lengths_array_len = + advisory_id_lengths_bytes.len() as u32; + qe_tcb_level_header.advisory_ids_data_total_len = + advisory_id_data_bytes.len() as u32; + + payload_bytes.extend_from_slice(bytemuck::bytes_of(&qe_tcb_level_header)); + current_payload_offset += mem::size_of::(); + + payload_bytes.extend_from_slice(&advisory_id_lengths_bytes); + current_payload_offset += advisory_id_lengths_bytes.len(); + + payload_bytes.extend_from_slice(&advisory_id_data_bytes); + current_payload_offset += advisory_id_data_bytes.len(); + + // Add padding for the *next* QeTcbLevelPodHeader + let padding_added = append_padding_to_align( + &mut payload_bytes, + mem::align_of::() // Align to 8 + ); + current_payload_offset += padding_added; + } + header.tcb_levels_total_payload_len = + (current_payload_offset - tcb_levels_payload_start_offset) as u32; + + Ok(SerializedEnclaveIdentity { + header, + payload: payload_bytes, + }) + } +} + +/// Serializes an EnclaveIdentityPod into a byte vector. +/// The layout will be: signature | EnclaveIdentityHeader | payload. +pub fn serialize_enclave_identity_pod( + serialized_ei: &SerializedEnclaveIdentity, + signature: &[u8; 64], +) -> Vec { + let header_bytes = bytemuck::bytes_of(&serialized_ei.header); + let mut pod_bytes = Vec::with_capacity( + signature.len() + header_bytes.len() + serialized_ei.payload.len(), + ); + pod_bytes.extend_from_slice(signature); + pod_bytes.extend_from_slice(header_bytes); + pod_bytes.extend_from_slice(&serialized_ei.payload); + pod_bytes +} + +/// Parses a byte slice representing an EnclaveIdentityPod into an application-level EnclaveIdentity and the signature. +/// Expects bytes in the layout: signature | EnclaveIdentityHeader | payload. +pub fn parse_enclave_identity_pod_bytes(pod_bytes: &[u8]) -> Result<(EnclaveIdentity, [u8; 64]), String> { + let signature_len = 64; + let header_len = mem::size_of::(); + let min_len = signature_len + header_len; + + if pod_bytes.len() < min_len { + return Err(format!( + "Byte slice too short for EnclaveIdentityPod. Expected at least {} bytes, got {}", + min_len, + pod_bytes.len() + )); + } + + let signature_slice = pod_bytes + .get(..signature_len) + .ok_or_else(|| "Failed to slice signature".to_string())?; + let mut signature = [0u8; 64]; + signature.copy_from_slice(signature_slice); + + let ei_header_and_payload_bytes = pod_bytes + .get(signature_len..) + .ok_or_else(|| "Failed to slice Enclave Identity header and payload".to_string())?; + + // This part uses the zero-copy parsing and then converts to the rich Rust struct. + // This ensures that the parsing logic (especially for complex payloads) is centralized + // in the zero_copy module. + let ei_zero_copy_view = EnclaveIdentityZeroCopy::from_bytes(ei_header_and_payload_bytes) + .map_err(|e| format!("Failed to create EnclaveIdentityZeroCopy view: {:?}", e))?; + + let rust_ei = enclave_identity_from_zero_copy(&ei_zero_copy_view) + .map_err(|e| format!("Failed to convert EnclaveIdentityZeroCopy to EnclaveIdentity: {:?}", e))?; + + Ok((rust_ei, signature)) +} diff --git a/src/types/pod/enclave_identity/tests.rs b/src/types/pod/enclave_identity/tests.rs new file mode 100644 index 0000000..f080cb7 --- /dev/null +++ b/src/types/pod/enclave_identity/tests.rs @@ -0,0 +1,140 @@ +// src/types/pod/enclave_identity/tests.rs +use crate::types::enclave_identity::{QeTcbStatus, QuotingEnclaveIdentityAndSignature}; +use crate::types::pod::enclave_identity::serialize::{ + SerializedEnclaveIdentity, parse_enclave_identity_pod_bytes, serialize_enclave_identity_pod, +}; +use crate::types::pod::enclave_identity::zero_copy::EnclaveIdentityZeroCopy; +use sha2::{Digest, Sha256}; + +#[test] +fn test_enclave_identity_bytemuck() { + let enclave_identity_data = include_bytes!("../../../../data/identityv2_0.json"); + + println!("original data size: {}", enclave_identity_data.len()); + + let enclave_identity_parsed: QuotingEnclaveIdentityAndSignature = + serde_json::from_slice(enclave_identity_data).unwrap(); + let original_enclave_identity = enclave_identity_parsed.get_enclave_identity().unwrap(); + let mut signature: [u8; 64] = [0; 64]; + signature.copy_from_slice(&enclave_identity_parsed.signature); + + let serialized_enclave_identity = + SerializedEnclaveIdentity::from_rust_enclave_identity(&original_enclave_identity).unwrap(); + let pod_bytes = serialize_enclave_identity_pod(&serialized_enclave_identity, &signature); + + println!("serialized data size: {}", pod_bytes.len()); + + let (parsed_enclave_identity, parsed_signature) = + parse_enclave_identity_pod_bytes(&pod_bytes).unwrap(); + + assert_eq!(original_enclave_identity, parsed_enclave_identity); + assert_eq!(&signature, &parsed_signature); + + // Integrity check + let original_hash = Sha256::digest( + enclave_identity_parsed + .enclave_identity_raw + .get() + .as_bytes(), + ); + let parsed_enclave_identity_string = serde_json::to_string(&parsed_enclave_identity).unwrap(); + let parsed_hash = Sha256::digest(parsed_enclave_identity_string.as_bytes()); + assert_eq!(original_hash, parsed_hash); + + // Test ZeroCopy + let enclave_identity_zero_copy_bytes = &pod_bytes[64..]; + let enclave_identity_zero_copy = + EnclaveIdentityZeroCopy::from_bytes(enclave_identity_zero_copy_bytes).unwrap(); + + assert_eq!( + enclave_identity_zero_copy.id_byte(), + u8::from(original_enclave_identity.id) + ); + + assert_eq!( + enclave_identity_zero_copy.version(), + original_enclave_identity.version + ); + + assert_eq!( + enclave_identity_zero_copy.issue_date_timestamp(), + original_enclave_identity.issue_date.timestamp() + ); + + assert_eq!( + enclave_identity_zero_copy.next_update_timestamp(), + original_enclave_identity.next_update.timestamp() + ); + + assert_eq!( + enclave_identity_zero_copy.tcb_evaluation_data_number(), + original_enclave_identity.tcb_evaluation_data_number + ); + + assert_eq!( + enclave_identity_zero_copy.miscselect_bytes(), + original_enclave_identity.miscselect_bytes() + ); + + assert_eq!( + enclave_identity_zero_copy.miscselect_mask_bytes(), + original_enclave_identity.miscselect_mask_bytes() + ); + + assert_eq!( + enclave_identity_zero_copy.attributes_bytes(), + original_enclave_identity.attributes_bytes() + ); + + assert_eq!( + enclave_identity_zero_copy.attributes_mask_bytes(), + original_enclave_identity.attributes_mask_bytes() + ); + + assert_eq!( + enclave_identity_zero_copy.mrsigner_bytes(), + original_enclave_identity.mrsigner_bytes() + ); + + assert_eq!( + enclave_identity_zero_copy.isvprodid(), + original_enclave_identity.isvprodid + ); + + let mut enclave_identity_zero_copy_tcb_levels_iter = enclave_identity_zero_copy.tcb_levels(); + let original_enclave_identity_tcb_levels = &original_enclave_identity.tcb_levels; + + assert_eq!( + enclave_identity_zero_copy.tcb_levels_count(), + original_enclave_identity_tcb_levels.len() as u32 + ); + + for tcb_level in original_enclave_identity_tcb_levels.iter() { + let zero_copy_tcb_level = enclave_identity_zero_copy_tcb_levels_iter + .next() + .unwrap() + .expect("Failed to get next ZeroCopy TCB level"); + assert_eq!(zero_copy_tcb_level.isvsvn(), tcb_level.tcb.isvsvn); + assert_eq!( + zero_copy_tcb_level.tcb_date_timestamp(), + tcb_level.tcb_date.timestamp() + ); + assert_eq!( + QeTcbStatus::try_from(zero_copy_tcb_level.tcb_status_byte()).unwrap(), + tcb_level.tcb_status + ); + + if let Some(advisory_ids) = tcb_level.advisory_ids.as_ref() { + let mut zero_copy_advisory_ids_iter = zero_copy_tcb_level.advisory_ids(); + for advisory_id in advisory_ids.iter() { + let zero_copy_advisory_id = zero_copy_advisory_ids_iter + .next() + .unwrap() + .expect("Failed to get next ZeroCopy advisory ID"); + assert_eq!(zero_copy_advisory_id, advisory_id); + } + } else { + assert_eq!(zero_copy_tcb_level.advisory_ids_count(), 0); + } + } +} diff --git a/src/types/pod/enclave_identity/zero_copy/conversion.rs b/src/types/pod/enclave_identity/zero_copy/conversion.rs new file mode 100644 index 0000000..80600ff --- /dev/null +++ b/src/types/pod/enclave_identity/zero_copy/conversion.rs @@ -0,0 +1,102 @@ +// src/types/pod/enclave_identity/zero_copy/conversion.rs + +#![cfg(feature = "full")] + +use super::error::ZeroCopyError; +use super::structs::{EnclaveIdentityZeroCopy, QeTcbLevelZeroCopy}; +use crate::types::enclave_identity::{ + EnclaveIdentity, EnclaveType, QeTcb, QeTcbLevel, QeTcbStatus, +}; +use chrono::{TimeZone, Utc}; + +// Helper to convert fixed-size byte arrays (representing ASCII hex or plain strings) +// from ZeroCopy structs back to owned Strings. Stops at first null byte or end of array. +fn zero_copy_bytes_to_string(bytes: &[u8]) -> String { + let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); + String::from_utf8_lossy(&bytes[..len]).into_owned() +} + +// Helper to convert u8 back to EnclaveType +fn enclave_type_from_byte(byte: u8) -> Result { + match byte { + 0 => Ok(EnclaveType::Qe), + 1 => Ok(EnclaveType::Qve), + 2 => Ok(EnclaveType::TdQe), + _ => Err(ZeroCopyError::InvalidEnumValue), + } +} + +// Helper to convert u8 back to QeTcbStatus +fn qe_tcb_status_from_byte(byte: u8) -> Result { + match byte { + 0 => Ok(QeTcbStatus::UpToDate), + 1 => Ok(QeTcbStatus::SWHardeningNeeded), + 2 => Ok(QeTcbStatus::OutOfDate), + 3 => Ok(QeTcbStatus::OutOfDateConfigurationNeeded), + 4 => Ok(QeTcbStatus::ConfigurationNeeded), + 5 => Ok(QeTcbStatus::ConfigurationAndSWHardeningNeeded), + 6 => Ok(QeTcbStatus::Revoked), + 7 => Ok(QeTcbStatus::Unspecified), + _ => Err(ZeroCopyError::InvalidEnumValue), + } +} + +pub fn qe_tcb_level_from_zero_copy( + view: &QeTcbLevelZeroCopy, +) -> Result { + let mut advisory_ids_vec = Vec::new(); + if view.advisory_ids_count() > 0 { + for adv_id_res in view.advisory_ids() { + advisory_ids_vec.push(adv_id_res?.to_string()); + } + } + + Ok(QeTcbLevel { + tcb: QeTcb { + isvsvn: view.isvsvn(), + }, + tcb_date: Utc + .timestamp_opt(view.tcb_date_timestamp(), 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?, // Using InvalidOffset for timestamp errors + tcb_status: qe_tcb_status_from_byte(view.tcb_status_byte())?, + advisory_ids: if advisory_ids_vec.is_empty() { + None + } else { + Some(advisory_ids_vec) + }, + }) +} + +pub fn enclave_identity_from_zero_copy( + view: &EnclaveIdentityZeroCopy, +) -> Result { + let mut tcb_levels_vec = Vec::new(); + if view.tcb_levels_count() > 0 { + for tcb_level_view_res in view.tcb_levels() { + let tcb_level_view = tcb_level_view_res?; + tcb_levels_vec.push(qe_tcb_level_from_zero_copy(&tcb_level_view)?); + } + } + + Ok(EnclaveIdentity { + id: enclave_type_from_byte(view.id_byte())?, + version: view.version(), + issue_date: Utc + .timestamp_opt(view.issue_date_timestamp(), 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?, + next_update: Utc + .timestamp_opt(view.next_update_timestamp(), 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?, + tcb_evaluation_data_number: view.tcb_evaluation_data_number(), + miscselect: zero_copy_bytes_to_string(&view.header.miscselect_hex), + miscselect_mask: zero_copy_bytes_to_string(&view.header.miscselect_mask_hex), + attributes: zero_copy_bytes_to_string(&view.header.attributes_hex), + attributes_mask: zero_copy_bytes_to_string(&view.header.attributes_mask_hex), + mrsigner: zero_copy_bytes_to_string(&view.header.mrsigner_hex), + isvprodid: view.isvprodid(), + tcb_levels: tcb_levels_vec, + }) +} diff --git a/src/types/pod/enclave_identity/zero_copy/error.rs b/src/types/pod/enclave_identity/zero_copy/error.rs new file mode 100644 index 0000000..024f898 --- /dev/null +++ b/src/types/pod/enclave_identity/zero_copy/error.rs @@ -0,0 +1,34 @@ +// src/types/pod/enclave_identity/zero_copy/error.rs + +use bytemuck::PodCastError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ZeroCopyError { + InvalidSliceLength, + InvalidOffset, + InvalidEnumValue, + InvalidUtf8, + BytemuckError(PodCastError), + // Add other specific errors as needed +} + +impl ZeroCopyError { + pub fn from_bytemuck_error(e: PodCastError) -> Self { + ZeroCopyError::BytemuckError(e) + } +} + +impl core::fmt::Display for ZeroCopyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ZeroCopyError::InvalidSliceLength => write!(f, "Invalid slice length encountered during zero-copy parsing"), + ZeroCopyError::InvalidOffset => write!(f, "Invalid offset calculated during zero-copy parsing"), + ZeroCopyError::InvalidEnumValue => write!(f, "Invalid enum value encountered"), + ZeroCopyError::InvalidUtf8 => write!(f, "Invalid UTF-8 sequence encountered in string data"), + ZeroCopyError::BytemuckError(e) => write!(f, "Bytemuck PodCastError: {:?}", e), + } + } +} + +// Implement std::error::Error if it's intended to be used with `anyhow` or similar. +// impl std::error::Error for ZeroCopyError {} diff --git a/src/types/pod/enclave_identity/zero_copy/iterators.rs b/src/types/pod/enclave_identity/zero_copy/iterators.rs new file mode 100644 index 0000000..5679b44 --- /dev/null +++ b/src/types/pod/enclave_identity/zero_copy/iterators.rs @@ -0,0 +1,172 @@ +// src/types/pod/enclave_identity/zero_copy/iterators.rs + +use super::error::ZeroCopyError; +use super::structs::QeTcbLevelZeroCopy; +use crate::types::pod::enclave_identity::QeTcbLevelPodHeader; +use bytemuck::Pod; +use core::{mem, str}; + +// Helper from structs.rs - consider moving to a shared util +#[inline] +fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { + bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) +} + +// --- Iterators --- + +// Iterator for Advisory IDs (string slices) +// This can be identical to the one in TcbInfo if no specific changes are needed. +pub struct AdvisoryIdIter<'a> { + lengths_payload: &'a [u8], // Slice containing array of u16 lengths + data_payload: &'a [u8], // Slice containing concatenated string data + count: u32, + current_idx: u32, + current_data_offset: usize, +} + +impl<'a> AdvisoryIdIter<'a> { + pub(super) fn new(lengths_payload: &'a [u8], data_payload: &'a [u8], count: u32) -> Self { + Self { + lengths_payload, + data_payload, + count, + current_idx: 0, + current_data_offset: 0, + } + } +} + +impl<'a> Iterator for AdvisoryIdIter<'a> { + type Item = Result<&'a str, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + + let len_size = core::mem::size_of::(); + let len_offset = (self.current_idx as usize).checked_mul(len_size)?; + let len_bytes_end = len_offset.checked_add(len_size)?; + + if len_bytes_end > self.lengths_payload.len() { + // Not enough data in lengths_payload for this index + self.current_idx = self.count; // Exhaust iterator + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let len_bytes_slice = self.lengths_payload.get(len_offset..len_bytes_end)?; + let len = u16::from_le_bytes(match len_bytes_slice.try_into() { + Ok(arr) => arr, + Err(_) => { // Should not happen if previous check passed + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + }) as usize; + + let data_end = self.current_data_offset.checked_add(len)?; + if data_end > self.data_payload.len() { + // Not enough data in data_payload for this string + self.current_idx = self.count; // Exhaust iterator + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let data_slice = self.data_payload.get(self.current_data_offset..data_end)?; + let res = str::from_utf8(data_slice).map_err(|_| ZeroCopyError::InvalidUtf8); + + self.current_data_offset = data_end; + self.current_idx += 1; + Some(res) + } +} + +// Iterator for QeTcbLevelZeroCopy +pub struct QeTcbLevelIter<'a> { + full_payload: &'a [u8], // This is the payload for *all* QeTcbLevels + count: u32, + current_idx: u32, + current_offset: usize, +} + +impl<'a> QeTcbLevelIter<'a> { + pub(super) fn new(full_payload: &'a [u8], count: u32) -> Self { + Self { + full_payload, + count, + current_idx: 0, + current_offset: 0, + } + } +} + +impl<'a> Iterator for QeTcbLevelIter<'a> { + type Item = Result, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + + let header_size = mem::size_of::(); + let header_slice_end = match self.current_offset.checked_add(header_size) { + Some(end) => end, + None => { // Offset calculation overflow + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidOffset)); + } + }; + + if header_slice_end > self.full_payload.len() { + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let header_slice = &self.full_payload[self.current_offset..header_slice_end]; + let header: &'a QeTcbLevelPodHeader = match cast_slice(header_slice) { + Ok(h) => h, + Err(e) => { + self.current_idx = self.count; + return Some(Err(e)); + } + }; + + let current_item_internal_payload_start = header_slice_end; + + let adv_ids_lengths_len = header.advisory_ids_lengths_array_len as usize; + let adv_ids_data_len = header.advisory_ids_data_total_len as usize; + + let actual_item_payload_len = match adv_ids_lengths_len.checked_add(adv_ids_data_len) { + Some(len) => len, + None => { // Offset calculation overflow + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidOffset)); + } + }; + + let item_payload_actual_end = + match current_item_internal_payload_start.checked_add(actual_item_payload_len) { + Some(end) => end, + None => { // Offset calculation overflow + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidOffset)); + } + }; + + if item_payload_actual_end > self.full_payload.len() { + self.current_idx = self.count; + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + let item_payload_slice = + &self.full_payload[current_item_internal_payload_start..item_payload_actual_end]; + + let view_result = QeTcbLevelZeroCopy::new(header, item_payload_slice); + + self.current_offset = item_payload_actual_end; // End of actual data for this item + + // Align self.current_offset for the NEXT header + let align_to = mem::align_of::(); // Align to 8 + self.current_offset = (self.current_offset + align_to - 1) & !(align_to - 1); + + self.current_idx += 1; + Some(view_result) + } +} diff --git a/src/types/pod/enclave_identity/zero_copy/mod.rs b/src/types/pod/enclave_identity/zero_copy/mod.rs new file mode 100644 index 0000000..760027c --- /dev/null +++ b/src/types/pod/enclave_identity/zero_copy/mod.rs @@ -0,0 +1,17 @@ +// src/types/pod/enclave_identity/zero_copy/mod.rs + +pub mod error; +pub mod structs; +pub mod iterators; + +#[cfg(feature = "full")] +pub mod conversion; + +pub use error::ZeroCopyError; // error.rs is a submodule +pub use structs::{ // structs.rs is a submodule + EnclaveIdentityZeroCopy, + QeTcbLevelZeroCopy, +}; +// We might not need to export iterators directly if they are only used internally +// by methods on the ZeroCopy structs. +// pub use iterators::*; // Example if iterators need to be named by users diff --git a/src/types/pod/enclave_identity/zero_copy/structs.rs b/src/types/pod/enclave_identity/zero_copy/structs.rs new file mode 100644 index 0000000..30f8549 --- /dev/null +++ b/src/types/pod/enclave_identity/zero_copy/structs.rs @@ -0,0 +1,134 @@ +// src/types/pod/enclave_identity/zero_copy/structs.rs + +use super::error::ZeroCopyError; +use super::iterators::{QeTcbLevelIter, AdvisoryIdIter}; // Will define iterators later +use crate::types::pod::enclave_identity::{ + EnclaveIdentityHeader, QeTcbLevelPodHeader, +}; +use bytemuck::Pod; + +// Helper to cast slices (can be moved to a shared util if used more widely) +#[inline] +fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { + bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) +} + +// --- Top-Level ZeroCopy Struct --- + +#[derive(Debug, Copy, Clone)] +pub struct EnclaveIdentityZeroCopy<'a> { + pub header: &'a EnclaveIdentityHeader, + tcb_levels_section_payload: &'a [u8], // Payload for all QeTcbLevel items +} + +impl<'a> EnclaveIdentityZeroCopy<'a> { + /// Creates a zero-copy view from a byte slice that starts with EnclaveIdentityHeader + /// and is followed by its complete payload. + pub fn from_bytes(bytes: &'a [u8]) -> Result { + if bytes.len() < core::mem::size_of::() { + return Err(ZeroCopyError::InvalidSliceLength); + } + let (header_bytes, main_payload) = bytes.split_at(core::mem::size_of::()); + let header: &EnclaveIdentityHeader = cast_slice(header_bytes)?; + + let levels_len = header.tcb_levels_total_payload_len as usize; + let tcb_levels_section_payload = main_payload.get(0..levels_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + + if levels_len > main_payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + + Ok(Self { + header, + tcb_levels_section_payload, + }) + } + + // --- Direct Header Accessors --- + pub fn id_byte(&self) -> u8 { self.header.id } + pub fn version(&self) -> u32 { self.header.version } + pub fn issue_date_timestamp(&self) -> i64 { self.header.issue_date_timestamp } + pub fn next_update_timestamp(&self) -> i64 { self.header.next_update_timestamp } + pub fn tcb_evaluation_data_number(&self) -> u16 { self.header.tcb_evaluation_data_number } + pub fn isvprodid(&self) -> u16 { self.header.isvprodid } + pub fn miscselect_bytes(&self) -> [u8; 4] { + hex::decode(&self.header.miscselect_hex) + .expect("Failed to decode miscselect_hex") + .try_into() + .expect("Failed to convert miscselect_hex to byte array") + } + pub fn miscselect_mask_bytes(&self) -> [u8; 4] { + hex::decode(&self.header.miscselect_mask_hex) + .expect("Failed to decode miscselect_mask_hex") + .try_into() + .expect("Failed to convert miscselect_mask_hex to byte array") + } + pub fn attributes_bytes(&self) -> [u8; 16] { + hex::decode(&self.header.attributes_hex) + .expect("Failed to decode attributes_hex") + .try_into() + .expect("Failed to convert attributes_hex to byte array") + } + pub fn attributes_mask_bytes(&self) -> [u8; 16] { + hex::decode(&self.header.attributes_mask_hex) + .expect("Failed to decode attributes_mask_hex") + .try_into() + .expect("Failed to convert attributes_mask_hex to byte array") + } + pub fn mrsigner_bytes(&self) -> [u8; 32] { + hex::decode(&self.header.mrsigner_hex) + .expect("Failed to decode mrsigner_hex") + .try_into() + .expect("Failed to convert mrsigner_hex to byte array") + } + + // --- Parsed/Structured Accessors --- + pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels(&self) -> QeTcbLevelIter<'a> { + QeTcbLevelIter::new( + self.tcb_levels_section_payload, + self.header.tcb_levels_count, + ) + } +} + +// --- QeTcbLevelZeroCopy --- +#[derive(Debug, Copy, Clone)] +pub struct QeTcbLevelZeroCopy<'a> { + header: &'a QeTcbLevelPodHeader, + advisory_ids_lengths_payload: &'a [u8], + advisory_ids_data_payload: &'a [u8], +} + +impl<'a> QeTcbLevelZeroCopy<'a> { + // 'payload' is specific to this QeTcbLevel's advisory IDs (lengths array + data) + pub fn new(header: &'a QeTcbLevelPodHeader, payload: &'a [u8]) -> Result { + let lengths_len = header.advisory_ids_lengths_array_len as usize; + let data_len = header.advisory_ids_data_total_len as usize; + + let total_adv_payload_len = lengths_len.checked_add(data_len).ok_or(ZeroCopyError::InvalidOffset)?; + if total_adv_payload_len > payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + + Ok(Self { + header, + advisory_ids_lengths_payload: &payload[..lengths_len], + advisory_ids_data_payload: &payload[lengths_len .. lengths_len + data_len], + }) + } + + pub fn isvsvn(&self) -> u16 { self.header.isvsvn } + pub fn tcb_status_byte(&self) -> u8 { self.header.tcb_status } + pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } + pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + + pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { + AdvisoryIdIter::new( + self.advisory_ids_lengths_payload, + self.advisory_ids_data_payload, + self.header.advisory_ids_count, + ) + } +} diff --git a/src/types/pod/mod.rs b/src/types/pod/mod.rs new file mode 100644 index 0000000..7e2f229 --- /dev/null +++ b/src/types/pod/mod.rs @@ -0,0 +1,2 @@ +pub mod tcb_info; +pub mod enclave_identity; \ No newline at end of file diff --git a/src/types/pod/tcb_info/mod.rs b/src/types/pod/tcb_info/mod.rs new file mode 100644 index 0000000..6a6e636 --- /dev/null +++ b/src/types/pod/tcb_info/mod.rs @@ -0,0 +1,130 @@ +pub mod zero_copy; + +#[cfg(feature = "full")] +pub mod serialize; +#[cfg(all(test, not(feature = "zero-copy")))] +mod tests; + +use bytemuck::{Pod, Zeroable}; + +// --- New Header and Data Structs --- + +// Size = 8 bytes, Alignment = 1 byte +/// Header for a TCB Component. +/// The actual string data for category and component_type will be in the parent's payload. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable, Default)] +pub struct TcbComponentHeader { + pub cpusvn: u8, + pub category_len: u8, + pub component_type_len: u8, + pub _padding: [u8; 5], +} + +// Size = 32 bytes, Alignment = 8 bytes +/// Header for a TDX TCB Level. +/// The actual advisory ID strings and their lengths array will be in this header's payload. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable, Default)] +pub struct TdxTcbLevelHeader { + pub tcb_isvsvn: u8, + pub tcb_status: u8, + pub _padding0: [u8; 6], + pub tcb_date_timestamp: i64, + pub advisory_ids_count: u32, + pub advisory_ids_lengths_array_len: u32, + pub advisory_ids_data_total_len: u32, + pub _padding1: [u8; 4], +} + +// Size = 144 bytes, Alignment = 4 bytes +/// Header for a TDX Module Identity. +/// The ID string and its TdxTcbLevel headers/payloads will be in this header's payload. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct TdxModuleIdentityHeader { + pub mrsigner_hex: [u8; 96], // Hex string + pub attributes_hex: [u8; 16], // Hex string + pub attributes_mask_hex: [u8; 16], // Hex string + pub id_len: u8, // Length of the ID string (e.g., "TDX_01") + pub _padding0: [u8; 7], + pub tcb_levels_count: u32, // Number of TdxTcbLevelHeader + payload sections + pub tcb_levels_total_payload_len: u32, // Total byte length for all TdxTcbLevelHeader sections for this identity +} + +// Size = 128 bytes, Alignment = 1 byte +/// POD structure for TDX Module data. This is part of TcbInfoHeader's payload if tdx_module_present. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct TdxModulePodData { + pub mrsigner_hex: [u8; 96], // Hex string + pub attributes_hex: [u8; 16], // Hex string + pub attributes_mask_hex: [u8; 16], // Hex string +} + +// Size = 304 bytes, Alignment = 8 bytes +/// Header for a general TCB Level (SGX or TDX platform). +/// Component string data and advisory ID data will be in this header's payload. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable, Default)] +pub struct TcbLevelHeader { + pub tcb_status: u8, + pub _padding0_a: u8, + pub pce_svn: u16, + pub _padding1_a: [u8; 4], + pub tcb_date_timestamp: i64, + + pub sgx_tcb_components: [TcbComponentHeader; 16], + pub tdx_tcb_components_present: u8, // 1 if present, 0 if not + pub _padding2_a: [u8; 7], + pub tdx_tcb_components: [TcbComponentHeader; 16], // Valid if tdx_tcb_components_present is 1 + + // Metadata for payload section of this TcbLevelHeader + pub sgx_components_strings_total_len: u32, // Sum of (category_len + component_type_len) for all 16 SGX components + pub tdx_components_strings_total_len: u32, // Sum for TDX components, if present (0 otherwise) + pub advisory_ids_count: u32, + pub advisory_ids_lengths_array_len: u32, // Total byte length of the array of u16 lengths + pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings + pub _final_padding: [u8; 4], +} + +// Size = 80 bytes, Alignment = 8 bytes +/// Main header for TCB Info. +/// This is followed by a variable-length payload containing data for tdx_module, +/// tdx_module_identities, and tcb_levels. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable, Default)] +pub struct TcbInfoHeader { + pub id_type: [u8; 6], + pub _pad_id_type: [u8; 2], + pub version: u32, + pub _pad_version: [u8; 4], + pub issue_date_timestamp: i64, + pub next_update_timestamp: i64, + pub fmspc_hex: [u8; 12], + pub pce_id_hex: [u8; 4], + pub tcb_type: u8, + pub _pad_tcb_type: [u8; 3], + pub tcb_evaluation_data_number: u32, + + pub tdx_module_present: u8, + pub _pad_tdx_module_present: [u8; 3], + pub tdx_module_data_len: u32, + + pub tdx_module_identities_count: u32, + pub tdx_module_identities_total_payload_len: u32, + + pub tcb_levels_count: u32, + pub tcb_levels_total_payload_len: u32, +} + +// Size = 144 bytes, Alignment = 8 bytes +/// The top-level POD structure for TCB Info and its signature. +/// The variable payload for TcbInfoHeader follows it directly in memory. +#[repr(C)] +#[derive(Copy, Clone, Debug, Pod, Zeroable)] +pub struct TcbPod { + pub signature: [u8; 64], + pub tcb_info_header: TcbInfoHeader, + // The variable payload associated with tcb_info_header follows here in memory. +} diff --git a/src/types/pod/tcb_info/serialize.rs b/src/types/pod/tcb_info/serialize.rs new file mode 100644 index 0000000..aefb68c --- /dev/null +++ b/src/types/pod/tcb_info/serialize.rs @@ -0,0 +1,326 @@ +use super::zero_copy::TcbInfoZeroCopy; +use super::zero_copy::conversion::tcb_info_from_zero_copy; +use super::{ + TcbInfoHeader, TcbLevelHeader, TdxModuleIdentityHeader, TdxModulePodData, TdxTcbLevelHeader, +}; +use crate::types::tcb_info::{Tcb, TcbInfo}; + +use bytemuck::Zeroable; +use core::mem; // For align_of + +// Helper function to append padding (null bytes) to `bytes_vec` so its new total length +// becomes a multiple of `align_to`. +// Returns the number of padding bytes that were added. +fn append_padding_to_align(bytes_vec: &mut Vec, align_to: usize) -> usize { + let current_len = bytes_vec.len(); + let remainder = current_len % align_to; + let mut padding_bytes_added = 0; + if remainder != 0 { + padding_bytes_added = align_to - remainder; + for _ in 0..padding_bytes_added { + bytes_vec.push(0); // Add null bytes for padding + } + } + padding_bytes_added +} + +// Helper function to copy string to fixed-size byte array, null-padding if shorter, truncating if longer. +fn string_to_fixed_bytes(s: &str) -> [u8; N] { + let mut arr = [0u8; N]; + let bytes = s.as_bytes(); + let len = bytes.len().min(N); + arr[..len].copy_from_slice(&bytes[..len]); + arr +} + +// Helper function to copy a hex string's ASCII characters to a fixed-size byte array. +// It null-pads if the string is shorter and truncates if longer. +fn hex_chars_to_fixed_bytes(hex_s: &str) -> [u8; N] { + let mut arr = [0u8; N]; + let s_bytes = hex_s.as_bytes(); + let len_to_copy = s_bytes.len().min(N); + arr[..len_to_copy].copy_from_slice(&s_bytes[..len_to_copy]); + arr +} + +/// Represents the combined TCB Info Header and its serialized payload. +pub struct SerializedTcbInfo { + pub header: TcbInfoHeader, + pub payload: Vec, +} + +impl SerializedTcbInfo { + /// Creates a SerializedTcbInfo from an application-level TcbInfo struct. + pub fn from_rust_tcb_info(rust_tcb_info: &TcbInfo) -> Result { + let mut header = TcbInfoHeader::zeroed(); + let mut payload_bytes = Vec::new(); + let mut current_payload_offset = 0; + + header.id_type = string_to_fixed_bytes::<6>(rust_tcb_info.id.as_deref().unwrap_or("")); + header.version = rust_tcb_info.version as u32; // TcbInfoVersion derives Copy + header.issue_date_timestamp = rust_tcb_info.issue_date.timestamp(); + header.next_update_timestamp = rust_tcb_info.next_update.timestamp(); + header.fmspc_hex = hex_chars_to_fixed_bytes::<12>(&rust_tcb_info.fmspc); + header.pce_id_hex = hex_chars_to_fixed_bytes::<4>(&rust_tcb_info.pce_id); + header.tcb_type = rust_tcb_info.tcb_type; + header.tcb_evaluation_data_number = rust_tcb_info.tcb_evaluation_data_number; + + if let Some(tdx_module) = &rust_tcb_info.tdx_module { + header.tdx_module_present = 1; + let pod_data = TdxModulePodData { + mrsigner_hex: hex_chars_to_fixed_bytes::<96>(&tdx_module.mrsigner), + attributes_hex: hex_chars_to_fixed_bytes::<16>(&tdx_module.attributes), + attributes_mask_hex: hex_chars_to_fixed_bytes::<16>(&tdx_module.attributes_mask), + }; + let tdx_module_bytes = bytemuck::bytes_of(&pod_data); + payload_bytes.extend_from_slice(tdx_module_bytes); + header.tdx_module_data_len = tdx_module_bytes.len() as u32; + current_payload_offset += tdx_module_bytes.len(); + } else { + header.tdx_module_present = 0; + header.tdx_module_data_len = 0; + } + + let tdx_module_identities_payload_start = current_payload_offset; + if let Some(identities) = &rust_tcb_info.tdx_module_identities { + header.tdx_module_identities_count = identities.len() as u32; + for identity in identities { + let mut identity_header = TdxModuleIdentityHeader::zeroed(); + identity_header.mrsigner_hex = hex_chars_to_fixed_bytes::<96>(&identity.mrsigner); + identity_header.attributes_hex = + hex_chars_to_fixed_bytes::<16>(&identity.attributes); + identity_header.attributes_mask_hex = + hex_chars_to_fixed_bytes::<16>(&identity.attributes_mask); + + let id_bytes = identity.id.as_bytes(); + identity_header.id_len = id_bytes.len() as u8; + + let mut tdx_tcb_levels_payload_for_identity = Vec::new(); + identity_header.tcb_levels_count = identity.tcb_levels.len() as u32; + for tdx_tcb_level in &identity.tcb_levels { + let mut tdx_tcb_level_header = TdxTcbLevelHeader::zeroed(); + tdx_tcb_level_header.tcb_isvsvn = tdx_tcb_level.tcb.isvsvn; + tdx_tcb_level_header.tcb_status = tdx_tcb_level.tcb_status as u8; + tdx_tcb_level_header.tcb_date_timestamp = + tdx_tcb_level.tcb_date.timestamp(); + + let mut advisory_id_lengths_bytes = Vec::new(); + let mut advisory_id_data_bytes = Vec::new(); + if let Some(advisory_ids) = &tdx_tcb_level.advisory_ids { + tdx_tcb_level_header.advisory_ids_count = advisory_ids.len() as u32; + for adv_id in advisory_ids { + let adv_id_bytes = adv_id.as_bytes(); + advisory_id_lengths_bytes + .extend_from_slice(&(adv_id_bytes.len() as u16).to_le_bytes()); + advisory_id_data_bytes.extend_from_slice(adv_id_bytes); + } + } else { + tdx_tcb_level_header.advisory_ids_count = 0; + } + tdx_tcb_level_header.advisory_ids_lengths_array_len = + advisory_id_lengths_bytes.len() as u32; + tdx_tcb_level_header.advisory_ids_data_total_len = + advisory_id_data_bytes.len() as u32; + + tdx_tcb_levels_payload_for_identity + .extend_from_slice(bytemuck::bytes_of(&tdx_tcb_level_header)); + tdx_tcb_levels_payload_for_identity + .extend_from_slice(&advisory_id_lengths_bytes); + tdx_tcb_levels_payload_for_identity.extend_from_slice(&advisory_id_data_bytes); + + // Add padding for the *next* TdxTcbLevelHeader in this sub-list + append_padding_to_align( + &mut tdx_tcb_levels_payload_for_identity, + mem::align_of::() // Align to 8 + ); + } + identity_header.tcb_levels_total_payload_len = + tdx_tcb_levels_payload_for_identity.len() as u32; + + payload_bytes.extend_from_slice(bytemuck::bytes_of(&identity_header)); + current_payload_offset += mem::size_of::(); + + payload_bytes.extend_from_slice(id_bytes); + current_payload_offset += id_bytes.len(); + + // Ensure the start of the TdxTcbLevel list (which follows id_bytes) is 8-byte aligned, + // as TdxTcbLevelHeader requires 8-byte alignment. + let padding_for_tdx_tcb_level_list = append_padding_to_align( + &mut payload_bytes, + mem::align_of::() // Align to 8 + ); + current_payload_offset += padding_for_tdx_tcb_level_list; + + payload_bytes.extend_from_slice(&tdx_tcb_levels_payload_for_identity); + current_payload_offset += tdx_tcb_levels_payload_for_identity.len(); + + // Add padding for the *next* TdxModuleIdentityHeader + let padding_added_for_next_identity = append_padding_to_align( + &mut payload_bytes, + mem::align_of::() // Align to 4 + ); + current_payload_offset += padding_added_for_next_identity; + } + } else { + header.tdx_module_identities_count = 0; + } + header.tdx_module_identities_total_payload_len = + (current_payload_offset - tdx_module_identities_payload_start) as u32; + + let tcb_levels_payload_start = current_payload_offset; + header.tcb_levels_count = rust_tcb_info.tcb_levels.len() as u32; + for rust_tcb_level in &rust_tcb_info.tcb_levels { + let mut tcb_level_header = TcbLevelHeader::zeroed(); + tcb_level_header.tcb_status = rust_tcb_level.tcb_status as u8; + tcb_level_header.pce_svn = rust_tcb_level.tcb.pcesvn(); + tcb_level_header.tcb_date_timestamp = rust_tcb_level.tcb_date.timestamp(); + + let mut sgx_components_payload_strings = Vec::new(); + let mut tdx_components_payload_strings_for_level = Vec::new(); + + match &rust_tcb_level.tcb { + Tcb::V3(tcb_v3) => { + for i in 0..16 { + let comp_header_ref = &mut tcb_level_header.sgx_tcb_components[i]; + comp_header_ref.cpusvn = tcb_v3.sgxtcbcomponents[i].svn; + let cat_str = tcb_v3.sgxtcbcomponents[i].category.as_deref().unwrap_or(""); + let type_str = tcb_v3.sgxtcbcomponents[i] + .component_type + .as_deref() + .unwrap_or(""); + comp_header_ref.category_len = cat_str.as_bytes().len() as u8; + comp_header_ref.component_type_len = type_str.as_bytes().len() as u8; + sgx_components_payload_strings.extend_from_slice(cat_str.as_bytes()); + sgx_components_payload_strings.extend_from_slice(type_str.as_bytes()); + } + if let Some(tdx_comps_v3) = &tcb_v3.tdxtcbcomponents { + tcb_level_header.tdx_tcb_components_present = 1; + for i in 0..16 { + let comp_header_ref = &mut tcb_level_header.tdx_tcb_components[i]; + comp_header_ref.cpusvn = tdx_comps_v3[i].svn; + let cat_str = tdx_comps_v3[i].category.as_deref().unwrap_or(""); + let type_str = tdx_comps_v3[i].component_type.as_deref().unwrap_or(""); + comp_header_ref.category_len = cat_str.as_bytes().len() as u8; + comp_header_ref.component_type_len = type_str.as_bytes().len() as u8; + tdx_components_payload_strings_for_level + .extend_from_slice(cat_str.as_bytes()); + tdx_components_payload_strings_for_level + .extend_from_slice(type_str.as_bytes()); + } + tcb_level_header.tdx_components_strings_total_len = + tdx_components_payload_strings_for_level.len() as u32; + } else { + tcb_level_header.tdx_tcb_components_present = 0; + tcb_level_header.tdx_components_strings_total_len = 0; + } + }, + Tcb::V2(_tcb_v2) => { + let svns = rust_tcb_level.tcb.sgx_tcb_components(); + for i in 0..16 { + let comp_header_ref = &mut tcb_level_header.sgx_tcb_components[i]; + comp_header_ref.cpusvn = svns[i]; + comp_header_ref.category_len = 0; + comp_header_ref.component_type_len = 0; + } + tcb_level_header.tdx_tcb_components_present = 0; + tcb_level_header.tdx_components_strings_total_len = 0; + }, + } + tcb_level_header.sgx_components_strings_total_len = + sgx_components_payload_strings.len() as u32; + + let mut tcb_level_advisory_id_lengths_bytes = Vec::new(); + let mut tcb_level_advisory_id_data_bytes = Vec::new(); + if let Some(advisory_ids) = &rust_tcb_level.advisory_ids { + tcb_level_header.advisory_ids_count = advisory_ids.len() as u32; + for adv_id in advisory_ids { + let adv_id_bytes = adv_id.as_bytes(); + tcb_level_advisory_id_lengths_bytes + .extend_from_slice(&(adv_id_bytes.len() as u16).to_le_bytes()); + tcb_level_advisory_id_data_bytes.extend_from_slice(adv_id_bytes); + } + } else { + tcb_level_header.advisory_ids_count = 0; + } + tcb_level_header.advisory_ids_lengths_array_len = + tcb_level_advisory_id_lengths_bytes.len() as u32; + tcb_level_header.advisory_ids_data_total_len = + tcb_level_advisory_id_data_bytes.len() as u32; + + payload_bytes.extend_from_slice(bytemuck::bytes_of(&tcb_level_header)); + current_payload_offset += core::mem::size_of::(); + + payload_bytes.extend_from_slice(&sgx_components_payload_strings); + current_payload_offset += sgx_components_payload_strings.len(); + + if tcb_level_header.tdx_tcb_components_present == 1 { + payload_bytes.extend_from_slice(&tdx_components_payload_strings_for_level); + current_payload_offset += tdx_components_payload_strings_for_level.len(); + } + payload_bytes.extend_from_slice(&tcb_level_advisory_id_lengths_bytes); + current_payload_offset += tcb_level_advisory_id_lengths_bytes.len(); + payload_bytes.extend_from_slice(&tcb_level_advisory_id_data_bytes); + current_payload_offset += tcb_level_advisory_id_data_bytes.len(); + + // Add padding for the *next* TcbLevelHeader + let padding_added_for_next_tcb_level = append_padding_to_align( + &mut payload_bytes, + mem::align_of::() // Align to 8 + ); + current_payload_offset += padding_added_for_next_tcb_level; + } + header.tcb_levels_total_payload_len = + (current_payload_offset - tcb_levels_payload_start) as u32; + + Ok(SerializedTcbInfo { + header, + payload: payload_bytes, + }) + } +} + +// --- TcbPod Serialization and Deserialization --- + +/// Serializes a TcbPod into a byte vector. +/// The layout will be: signature | TcbInfoHeader | payload. +pub fn serialize_tcb_pod(serialized_tcb_info: &SerializedTcbInfo, signature: &[u8; 64]) -> Vec { + let header_bytes = bytemuck::bytes_of(&serialized_tcb_info.header); + let mut tcb_pod_bytes = Vec::with_capacity( + signature.len() + header_bytes.len() + serialized_tcb_info.payload.len(), + ); + tcb_pod_bytes.extend_from_slice(signature); + tcb_pod_bytes.extend_from_slice(header_bytes); + tcb_pod_bytes.extend_from_slice(&serialized_tcb_info.payload); + tcb_pod_bytes +} + +/// Parses a byte slice representing a TcbPod into an application-level TcbInfo and the signature. +/// Expects bytes in the layout: signature | TcbInfoHeader | payload. +pub fn parse_tcb_pod_bytes(pod_bytes: &[u8]) -> Result<(TcbInfo, [u8; 64]), String> { + let min_len = core::mem::size_of::<[u8; 64]>() + core::mem::size_of::(); + if pod_bytes.len() < min_len { + return Err(format!( + "Byte slice too short for TcbPod. Expected at least {} bytes, got {}", + min_len, + pod_bytes.len() + )); + } + + let signature_slice = pod_bytes + .get(..64) + .ok_or_else(|| "Failed to slice signature".to_string())?; + let mut signature = [0u8; 64]; + signature.copy_from_slice(signature_slice); + + let tcb_info_and_payload_bytes = pod_bytes + .get(64..) + .ok_or_else(|| "Failed to slice TCB info header and payload".to_string())?; + + let tcb_info_view = TcbInfoZeroCopy::from_bytes(tcb_info_and_payload_bytes) + .map_err(|e| format!("Failed to create TcbInfoZeroCopy: {:?}", e))?; + + let rust_tcb_info = tcb_info_from_zero_copy(&tcb_info_view) + .map_err(|e| format!("Failed to convert TcbInfoZeroCopy to TcbInfo: {:?}", e))?; + + Ok((rust_tcb_info, signature)) +} diff --git a/src/types/pod/tcb_info/tests.rs b/src/types/pod/tcb_info/tests.rs new file mode 100644 index 0000000..bf92379 --- /dev/null +++ b/src/types/pod/tcb_info/tests.rs @@ -0,0 +1,539 @@ +use super::serialize::*; +use super::zero_copy::*; +use crate::types::tcb_info::*; + +use sha2::{Digest, Sha256}; + +#[test] +fn test_tcb_v2_sgx_bytemuck() { + let tcb_data = include_bytes!("../../../../data/tcb_info_v2.json"); + + println!("Original TCB data size: {}", tcb_data.len()); + + let tcb_info_and_signature = serde_json::from_slice::(tcb_data) + .expect("Failed to deserialize TCB info and signature"); + let original_tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); + let mut signature: [u8; 64] = [0u8; 64]; + signature.copy_from_slice(tcb_info_and_signature.signature.as_slice()); + + let serialized_tcb_info = SerializedTcbInfo::from_rust_tcb_info(&original_tcb_info) + .expect("Serialization to SerializedTcbInfo failed for TcbV2"); + let pod_bytes = serialize_tcb_pod(&serialized_tcb_info, &signature); + + println!("Serialized TCB data size: {}", pod_bytes.len()); + + let (parsed_tcb_info, parsed_signature) = + parse_tcb_pod_bytes(&pod_bytes).expect("Parsing TcbPod bytes failed for TcbV2"); + assert_eq!( + original_tcb_info, parsed_tcb_info, + "Round-tripped TcbInfoV2 does not match original" + ); + assert_eq!( + signature, parsed_signature, + "Round-tripped TcbInfoV2 signature does not match original" + ); + + // Integrity Check + let original_tcb_info_hash = + Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) + .expect("Failed to serialize parsed TcbInfoV2 to JSON string"); + let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); + assert_eq!( + original_tcb_info_hash, parsed_tcb_info_hash, + "Parsed TcbInfoV2 hash does not match original" + ); + + // Test ZeroCopy for TcbV2 + let tcb_info_header_and_payload_bytes = &pod_bytes[64..]; + let tcb_info_zero_copy = TcbInfoZeroCopy::from_bytes(tcb_info_header_and_payload_bytes) + .expect("Failed to create TcbInfoZeroCopy for TcbV2"); + + assert_eq!( + tcb_info_zero_copy.id_type_bytes(), + &[0u8; 6], + "ID Bytes should be zero for TcbV2" + ); + assert_eq!( + tcb_info_zero_copy.version(), + original_tcb_info.version as u32 + ); + assert_eq!( + tcb_info_zero_copy.issue_date_timestamp(), + original_tcb_info.issue_date.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.next_update_timestamp(), + original_tcb_info.next_update.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.fmspc(), + original_tcb_info.fmspc_bytes() + ); + assert_eq!( + tcb_info_zero_copy.pce_id(), + original_tcb_info.pce_id_bytes() + ); + assert_eq!(tcb_info_zero_copy.tcb_type(), original_tcb_info.tcb_type); + assert_eq!( + tcb_info_zero_copy.tcb_evaluation_data_number(), + original_tcb_info.tcb_evaluation_data_number + ); + assert!( + tcb_info_zero_copy.tdx_module().is_none(), + "TDX module should not be present in TcbV2" + ); + assert_eq!(tcb_info_zero_copy.tdx_module_identities_count(), 0); + + let mut view_tcb_levels_iter = tcb_info_zero_copy.tcb_levels(); + let original_tcb_levels = &original_tcb_info.tcb_levels; + + // TCB Levels length check + assert_eq!( + tcb_info_zero_copy.tcb_levels_count(), + original_tcb_levels.len() as u32 + ); + + // iterate over TCB Levels + for tcb_level in original_tcb_levels.iter() { + let view_tcb_level = view_tcb_levels_iter + .next() + .unwrap() + .expect("Failed to get TCB level from view"); + assert_eq!( + view_tcb_level.tcb_date_timestamp(), + tcb_level.tcb_date.timestamp() + ); + assert_eq!( + TcbStatus::try_from(view_tcb_level.tcb_status()).unwrap(), + tcb_level.tcb_status + ); + assert_eq!( + view_tcb_level.advisory_ids_count(), + 0, + "There should be no advisory IDs in TcbV2" + ); + assert_eq!(view_tcb_level.pce_svn(), tcb_level.tcb.pcesvn()); + + let sgx_components = tcb_level.tcb.sgx_tcb_components(); + for (i, view_sgx_component) in view_tcb_level.sgx_tcb_components().enumerate() { + let view_sgx_component = + view_sgx_component.expect("Failed to get SGX component from view"); + assert_eq!(view_sgx_component.cpusvn(), sgx_components[i]); + assert_eq!(view_sgx_component.category_str().unwrap(), ""); + assert_eq!(view_sgx_component.component_type_str().unwrap(), ""); + } + + assert!( + view_tcb_level.tdx_tcb_components().is_none(), + "TDX components should not be present in TcbV2" + ); + let view_advisory_ids_iter = view_tcb_level.advisory_ids(); + assert_eq!( + view_advisory_ids_iter.count(), + 0, + "Advisory IDs should not be present in TcbV2" + ); + } + assert_eq!( + view_tcb_levels_iter.count(), + 0, + "Expected no more TCB levels in the iterator" + ); +} + +#[test] +fn test_tcb_v3_sgx_bytemuck() { + let tcb_data = include_bytes!("../../../../data/tcb_info_v3_sgx.json"); + + println!("Original TCB data size: {}", tcb_data.len()); + + let tcb_info_and_signature = serde_json::from_slice::(tcb_data) + .expect("Failed to deserialize TCB info and signature"); + let original_tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); + let mut signature: [u8; 64] = [0u8; 64]; + signature.copy_from_slice(tcb_info_and_signature.signature.as_slice()); + + let serialized_tcb_info = SerializedTcbInfo::from_rust_tcb_info(&original_tcb_info) + .expect("Serialization to SerializedTcbInfo failed for TcbV3 SGX"); + let pod_bytes = serialize_tcb_pod(&serialized_tcb_info, &signature); + + println!("Serialized TCB data size: {}", pod_bytes.len()); + + let (parsed_tcb_info, parsed_signature) = + parse_tcb_pod_bytes(&pod_bytes).expect("Parsing TcbPod bytes failed for TcbV3 SGX"); + assert_eq!( + original_tcb_info, parsed_tcb_info, + "Round-tripped TcbInfoV3 does not match original" + ); + assert_eq!( + signature, parsed_signature, + "Round-tripped TcbInfoV3 signature does not match original" + ); + + // Integrity Check + let original_tcb_info_hash = + Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) + .expect("Failed to serialize parsed TcbInfoV3 SGX to JSON string"); + let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); + assert_eq!( + original_tcb_info_hash, parsed_tcb_info_hash, + "Parsed TcbInfoV2 hash does not match original" + ); + + // Test ZeroCopy for TcbV3 SGX + let tcb_info_header_and_payload_bytes = &pod_bytes[64..]; + let tcb_info_zero_copy = TcbInfoZeroCopy::from_bytes(tcb_info_header_and_payload_bytes) + .expect("Failed to create TcbInfoZeroCopy for TcbV3"); + + // assert_eq!(tcb_info_zero_copy.id_type_bytes(), b"SGX", "ID Bytes should be zero for TcbV3"); + assert_eq!( + tcb_info_zero_copy.version(), + original_tcb_info.version as u32 + ); + assert_eq!( + tcb_info_zero_copy.issue_date_timestamp(), + original_tcb_info.issue_date.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.next_update_timestamp(), + original_tcb_info.next_update.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.fmspc(), + original_tcb_info.fmspc_bytes() + ); + assert_eq!( + tcb_info_zero_copy.pce_id(), + original_tcb_info.pce_id_bytes() + ); + assert_eq!(tcb_info_zero_copy.tcb_type(), original_tcb_info.tcb_type); + assert_eq!( + tcb_info_zero_copy.tcb_evaluation_data_number(), + original_tcb_info.tcb_evaluation_data_number + ); + assert!( + tcb_info_zero_copy.tdx_module().is_none(), + "TDX module should not be present in TcbV3 SGX" + ); + assert_eq!(tcb_info_zero_copy.tdx_module_identities_count(), 0); + + let mut view_tcb_levels_iter = tcb_info_zero_copy.tcb_levels(); + let original_tcb_levels = &original_tcb_info.tcb_levels; + + // TCB Levels length check + assert_eq!( + tcb_info_zero_copy.tcb_levels_count(), + original_tcb_levels.len() as u32 + ); + + // iterate over TCB Levels + for tcb_level in original_tcb_levels.iter() { + let view_tcb_level = view_tcb_levels_iter + .next() + .unwrap() + .expect("Failed to get TCB level from view"); + assert_eq!( + view_tcb_level.tcb_date_timestamp(), + tcb_level.tcb_date.timestamp() + ); + assert_eq!( + TcbStatus::try_from(view_tcb_level.tcb_status()).unwrap(), + tcb_level.tcb_status + ); + assert_eq!(view_tcb_level.pce_svn(), tcb_level.tcb.pcesvn()); + + let advisory_ids = tcb_level.advisory_ids.as_ref(); + if let Some(advisory_ids) = advisory_ids { + assert_eq!( + view_tcb_level.advisory_ids_count(), + advisory_ids.len() as u32 + ); + let view_advisory_ids_iter = view_tcb_level.advisory_ids(); + for (i, view_advisory_id) in view_advisory_ids_iter.enumerate() { + let view_advisory_id = + view_advisory_id.expect("Failed to get advisory ID from view"); + assert_eq!(view_advisory_id, advisory_ids[i].as_str()); + } + } + + let tcb_v3 = if let Tcb::V3(tcb) = &tcb_level.tcb { + tcb + } else { + panic!("Expected Tcb::V3 for SGX TCB level"); + }; + + for (i, view_sgx_component) in view_tcb_level.sgx_tcb_components().enumerate() { + let view_sgx_component = + view_sgx_component.expect("Failed to get SGX component from view"); + let sgx_component = &tcb_v3.sgxtcbcomponents[i]; + assert_eq!(view_sgx_component.cpusvn(), sgx_component.svn); + + if let Some(category) = &sgx_component.category { + assert_eq!(view_sgx_component.category_str().unwrap(), category); + } else { + assert_eq!(view_sgx_component.category_str().unwrap(), ""); + } + + if let Some(component_type) = &sgx_component.component_type { + assert_eq!( + view_sgx_component.component_type_str().unwrap(), + component_type + ); + } else { + assert_eq!(view_sgx_component.component_type_str().unwrap(), ""); + } + } + + assert!( + view_tcb_level.tdx_tcb_components().is_none(), + "TDX components should not be present in TcbV3 SGX" + ); + } + assert_eq!( + view_tcb_levels_iter.count(), + 0, + "Expected no more TCB levels in the iterator" + ); +} + +#[test] +fn test_tcb_v3_tdx_bytemuck() { + let tcb_data = include_bytes!("../../../../data/tcb_info_v3_tdx_0.json"); + + println!("Original TCB data size: {}", tcb_data.len()); + + let tcb_info_and_signature = serde_json::from_slice::(tcb_data) + .expect("Failed to deserialize TCB info and signature"); + let original_tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); + let mut signature: [u8; 64] = [0u8; 64]; + signature.copy_from_slice(tcb_info_and_signature.signature.as_slice()); + + let serialized_tcb_info = SerializedTcbInfo::from_rust_tcb_info(&original_tcb_info) + .expect("Serialization to SerializedTcbInfo failed for TcbV3 TDX"); + let pod_bytes = serialize_tcb_pod(&serialized_tcb_info, &signature); + + println!("Serialized TCB data size: {}", pod_bytes.len()); + + let (parsed_tcb_info, parsed_signature) = + parse_tcb_pod_bytes(&pod_bytes).expect("Parsing TcbPod bytes failed for TcbV3 TDX"); + assert_eq!( + original_tcb_info, parsed_tcb_info, + "Round-tripped TcbInfoV3 does not match original" + ); + assert_eq!( + signature, parsed_signature, + "Round-tripped TcbInfoV3 signature does not match original" + ); + + // Integrity Check + let original_tcb_info_hash = + Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) + .expect("Failed to serialize parsed TcbInfoV3 SGX to JSON string"); + let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); + assert_eq!( + original_tcb_info_hash, parsed_tcb_info_hash, + "Parsed TcbInfoV2 hash does not match original" + ); + + // Test ZeroCopy for TcbV3 TDX + let tcb_info_header_and_payload_bytes = &pod_bytes[64..]; + let tcb_info_zero_copy = TcbInfoZeroCopy::from_bytes(tcb_info_header_and_payload_bytes) + .expect("Failed to create TcbInfoZeroCopy for TcbV3"); + + // assert_eq!(tcb_info_zero_copy.id_type_bytes(), b"SGX", "ID Bytes should be zero for TcbV3"); + assert_eq!( + tcb_info_zero_copy.version(), + original_tcb_info.version as u32 + ); + assert_eq!( + tcb_info_zero_copy.issue_date_timestamp(), + original_tcb_info.issue_date.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.next_update_timestamp(), + original_tcb_info.next_update.timestamp() + ); + assert_eq!( + tcb_info_zero_copy.fmspc(), + original_tcb_info.fmspc_bytes() + ); + assert_eq!( + tcb_info_zero_copy.pce_id(), + original_tcb_info.pce_id_bytes() + ); + assert_eq!(tcb_info_zero_copy.tcb_type(), original_tcb_info.tcb_type); + assert_eq!( + tcb_info_zero_copy.tcb_evaluation_data_number(), + original_tcb_info.tcb_evaluation_data_number + ); + + if let Some(tdx_module) = tcb_info_zero_copy.tdx_module() { + assert_eq!( + tdx_module.mrsigner(), + original_tcb_info + .tdx_module + .as_ref() + .unwrap() + .mrsigner_bytes() + ); + assert_eq!( + tdx_module.attributes(), + original_tcb_info + .tdx_module + .as_ref() + .unwrap() + .attributes_bytes() + ); + assert_eq!( + tdx_module.attributes_mask(), + original_tcb_info + .tdx_module + .as_ref() + .unwrap() + .attributes_mask_bytes() + ); + } else { + panic!("TDX module should be present in TcbV3 TDX"); + } + + if tcb_info_zero_copy.tdx_module_identities_count() > 0 { + let tdx_module_identities_iter = tcb_info_zero_copy.tdx_module_identities().enumerate(); + let original_tdx_module_identities = &original_tcb_info.tdx_module_identities.unwrap(); + for (i, tdx_module_identity) in tdx_module_identities_iter { + let tdx_module_identity = + tdx_module_identity.expect("Failed to get TDX module identity from view"); + assert_eq!( + tdx_module_identity.mrsigner(), + original_tdx_module_identities[i] + .mrsigner_bytes() + ); + assert_eq!( + tdx_module_identity.attributes(), + original_tdx_module_identities[i].attributes_bytes() + ); + assert_eq!( + tdx_module_identity.attributes_mask(), + original_tdx_module_identities[i].attributes_mask_bytes() + ); + assert_eq!( + tdx_module_identity.id_str().unwrap(), + original_tdx_module_identities[i].id.as_str() + ); + + let tcb_module_identity_tcb_count = tdx_module_identity.tcb_levels_count(); + assert_eq!( + tcb_module_identity_tcb_count, + original_tdx_module_identities[i].tcb_levels.len() as u32 + ); + if tcb_module_identity_tcb_count > 0 { + let mut view_tcb_levels_iter = tdx_module_identity.tcb_levels(); + let original_tcb_levels = &original_tdx_module_identities[i].tcb_levels; + for tcb_level in original_tcb_levels.iter() { + let view_tcb_level = view_tcb_levels_iter + .next() + .unwrap() + .expect("Failed to get TDX Module Identity TCB level from view"); + assert_eq!( + view_tcb_level.tcb_date_timestamp(), + tcb_level.tcb_date.timestamp() + ); + assert_eq!( + TcbStatus::try_from(view_tcb_level.tcb_status()).unwrap(), + tcb_level.tcb_status + ); + assert_eq!(view_tcb_level.tcb_isvsvn(), tcb_level.tcb.isvsvn); + + let advisory_ids = tcb_level.advisory_ids.as_ref(); + if let Some(advisory_ids) = advisory_ids { + assert_eq!( + view_tcb_level.advisory_ids_count(), + advisory_ids.len() as u32 + ); + let view_advisory_ids_iter = view_tcb_level.advisory_ids(); + for (j, view_advisory_id) in view_advisory_ids_iter.enumerate() { + let view_advisory_id = + view_advisory_id.expect("Failed to get advisory ID from view"); + assert_eq!(view_advisory_id, advisory_ids[j].as_str()); + } + } + } + } + } + } + + let mut view_tcb_levels_iter = tcb_info_zero_copy.tcb_levels(); + let original_tcb_levels = &original_tcb_info.tcb_levels; + + // TCB Levels length check + assert_eq!( + tcb_info_zero_copy.tcb_levels_count(), + original_tcb_levels.len() as u32 + ); + + // iterate over TCB Levels + for tcb_level in original_tcb_levels.iter() { + let view_tcb_level = view_tcb_levels_iter + .next() + .unwrap() + .expect("Failed to get TCB level from view"); + assert_eq!( + view_tcb_level.tcb_date_timestamp(), + tcb_level.tcb_date.timestamp() + ); + assert_eq!( + TcbStatus::try_from(view_tcb_level.tcb_status()).unwrap(), + tcb_level.tcb_status + ); + assert_eq!(view_tcb_level.pce_svn(), tcb_level.tcb.pcesvn()); + + let advisory_ids = tcb_level.advisory_ids.as_ref(); + if let Some(advisory_ids) = advisory_ids { + assert_eq!( + view_tcb_level.advisory_ids_count(), + advisory_ids.len() as u32 + ); + let view_advisory_ids_iter = view_tcb_level.advisory_ids(); + for (i, view_advisory_id) in view_advisory_ids_iter.enumerate() { + let view_advisory_id = + view_advisory_id.expect("Failed to get advisory ID from view"); + assert_eq!(view_advisory_id, advisory_ids[i].as_str()); + } + } + + let tcb_v3 = if let Tcb::V3(tcb) = &tcb_level.tcb { + tcb + } else { + panic!("Expected Tcb::V3 for SGX TCB level"); + }; + + for (i, view_sgx_component) in view_tcb_level.sgx_tcb_components().enumerate() { + let view_sgx_component = + view_sgx_component.expect("Failed to get SGX component from view"); + let sgx_component = &tcb_v3.sgxtcbcomponents[i]; + assert_eq!(view_sgx_component.cpusvn(), sgx_component.svn); + + if let Some(category) = &sgx_component.category { + assert_eq!(view_sgx_component.category_str().unwrap(), category); + } else { + assert_eq!(view_sgx_component.category_str().unwrap(), ""); + } + + if let Some(component_type) = &sgx_component.component_type { + assert_eq!( + view_sgx_component.component_type_str().unwrap(), + component_type + ); + } else { + assert_eq!(view_sgx_component.component_type_str().unwrap(), ""); + } + } + } + assert_eq!( + view_tcb_levels_iter.count(), + 0, + "Expected no more TCB levels in the iterator" + ); +} diff --git a/src/types/pod/tcb_info/zero_copy/conversion.rs b/src/types/pod/tcb_info/zero_copy/conversion.rs new file mode 100644 index 0000000..2546c19 --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/conversion.rs @@ -0,0 +1,211 @@ +// src/types/pod/tcb_info/zero_copy/conversion.rs + +use super::error::ZeroCopyError; +use super::structs::*; +use crate::types::tcb_info::{ + Tcb, TcbComponentV3, TcbInfo, TcbInfoVersion, TcbLevel, TcbStatus, TcbTdx, TdxModule, + TdxModuleIdentity, TdxTcbLevel, +}; +use chrono::{TimeZone, Utc}; + +// Helper to convert fixed-size byte arrays (representing ASCII hex or plain strings) +// from ZeroCopy structs back to owned Strings. Stops at first null byte or end of array. +fn zero_copy_bytes_to_string(bytes: &[u8]) -> String { + let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); + String::from_utf8_lossy(&bytes[..len]).into_owned() +} + +// Helper to create an empty TcbComponentV3 for array initialization +fn empty_tcb_component_v3() -> TcbComponentV3 { + TcbComponentV3 { + svn: 0, + category: None, + component_type: None, + } +} + +pub fn tcb_info_from_zero_copy(view: &TcbInfoZeroCopy) -> Result { + let id_str = zero_copy_bytes_to_string(view.id_type_bytes()); + let id_option = if id_str.is_empty() { + None + } else { + Some(id_str) + }; + + let version = + TcbInfoVersion::try_from(view.version()).map_err(|_| ZeroCopyError::InvalidEnumValue)?; + + let issue_date = Utc + .timestamp_opt(view.issue_date_timestamp() as i64, 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?; + let next_update = Utc + .timestamp_opt(view.next_update_timestamp() as i64, 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?; + + let fmspc = zero_copy_bytes_to_string(&view.header.fmspc_hex); + let pce_id = zero_copy_bytes_to_string(&view.header.pce_id_hex); + + let tdx_module: Option = match view.tdx_module() { + Some(tdx_mod_view) => Some(TdxModule { + mrsigner: zero_copy_bytes_to_string(&tdx_mod_view.data.mrsigner_hex), + attributes: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_hex), + attributes_mask: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_mask_hex), + }), + None => None, + }; + + let mut tdx_module_identities_vec = Vec::new(); + if view.tdx_module_identities_count() > 0 { + for identity_view_res in view.tdx_module_identities() { + let identity_view = identity_view_res?; + let mut tdx_tcb_levels_for_identity_vec = Vec::new(); + if identity_view.tcb_levels_count() > 0 { + for tdx_tcb_level_view_res in identity_view.tcb_levels() { + let tdx_tcb_level_view = tdx_tcb_level_view_res?; + let mut advisory_ids_vec = Vec::new(); + if tdx_tcb_level_view.advisory_ids_count() > 0 { + for adv_id_res in tdx_tcb_level_view.advisory_ids() { + advisory_ids_vec.push(adv_id_res?.to_string()); + } + } + let tdx_tcb_level_app = TdxTcbLevel { + tcb: TcbTdx { + isvsvn: tdx_tcb_level_view.tcb_isvsvn(), + }, + tcb_date: Utc + .timestamp_opt(tdx_tcb_level_view.tcb_date_timestamp() as i64, 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?, + tcb_status: TcbStatus::try_from(tdx_tcb_level_view.tcb_status()) + .map_err(|_| ZeroCopyError::InvalidEnumValue)?, + advisory_ids: if advisory_ids_vec.is_empty() { + None + } else { + Some(advisory_ids_vec) + }, + }; + tdx_tcb_levels_for_identity_vec.push(tdx_tcb_level_app); + } + } + let identity_app = TdxModuleIdentity { + id: identity_view.id_str()?.to_string(), + mrsigner: zero_copy_bytes_to_string(&identity_view.header.mrsigner_hex), + attributes: zero_copy_bytes_to_string(&identity_view.header.attributes_hex), + attributes_mask: zero_copy_bytes_to_string( + &identity_view.header.attributes_mask_hex, + ), + tcb_levels: tdx_tcb_levels_for_identity_vec, + }; + tdx_module_identities_vec.push(identity_app); + } + } + let tdx_module_identities_option = if tdx_module_identities_vec.is_empty() { + None + } else { + Some(tdx_module_identities_vec) + }; + + let mut tcb_levels_vec = Vec::new(); + if view.tcb_levels_count() > 0 { + for tcb_level_view_res in view.tcb_levels() { + let tcb_level_view = tcb_level_view_res?; + + let mut sgx_components_app = core::array::from_fn(|_| empty_tcb_component_v3()); + for (i, comp_view_res) in tcb_level_view.sgx_tcb_components().enumerate() { + let comp_view = comp_view_res?; + sgx_components_app[i] = TcbComponentV3 { + svn: comp_view.cpusvn(), + category: Some(comp_view.category_str()?.to_string()).filter(|s| !s.is_empty()), + component_type: Some(comp_view.component_type_str()?.to_string()) + .filter(|s| !s.is_empty()), + }; + } + + let mut tdx_components_app_option: Option<[TcbComponentV3; 16]> = None; + if let Some(tdx_comp_iter) = tcb_level_view.tdx_tcb_components() { + let mut tdx_components_app_arr = core::array::from_fn(|_| empty_tcb_component_v3()); + for (i, comp_view_res) in tdx_comp_iter.enumerate() { + let comp_view = comp_view_res?; + tdx_components_app_arr[i] = TcbComponentV3 { + svn: comp_view.cpusvn(), + category: Some(comp_view.category_str()?.to_string()) + .filter(|s| !s.is_empty()), + component_type: Some(comp_view.component_type_str()?.to_string()) + .filter(|s| !s.is_empty()), + }; + } + tdx_components_app_option = Some(tdx_components_app_arr); + } + + let tcb_app = match version { + TcbInfoVersion::V3 => Tcb::V3(crate::types::tcb_info::TcbV3 { + sgxtcbcomponents: sgx_components_app, + pcesvn: tcb_level_view.pce_svn(), + tdxtcbcomponents: tdx_components_app_option, + }), + TcbInfoVersion::V2 => { + let mut sgx_svns = [0u8; 16]; + for i in 0..16 { + sgx_svns[i] = sgx_components_app[i].svn; + } + Tcb::V2(crate::types::tcb_info::TcbV2 { + sgxtcbcomp01svn: sgx_svns[0], + sgxtcbcomp02svn: sgx_svns[1], + sgxtcbcomp03svn: sgx_svns[2], + sgxtcbcomp04svn: sgx_svns[3], + sgxtcbcomp05svn: sgx_svns[4], + sgxtcbcomp06svn: sgx_svns[5], + sgxtcbcomp07svn: sgx_svns[6], + sgxtcbcomp08svn: sgx_svns[7], + sgxtcbcomp09svn: sgx_svns[8], + sgxtcbcomp10svn: sgx_svns[9], + sgxtcbcomp11svn: sgx_svns[10], + sgxtcbcomp12svn: sgx_svns[11], + sgxtcbcomp13svn: sgx_svns[12], + sgxtcbcomp14svn: sgx_svns[13], + sgxtcbcomp15svn: sgx_svns[14], + sgxtcbcomp16svn: sgx_svns[15], + pcesvn: tcb_level_view.pce_svn(), + }) + }, + }; + + let mut advisory_ids_vec = Vec::new(); + for adv_id_res in tcb_level_view.advisory_ids() { + advisory_ids_vec.push(adv_id_res?.to_string()); + } + + let tcb_level_app = TcbLevel { + tcb: tcb_app, + tcb_date: Utc + .timestamp_opt(tcb_level_view.tcb_date_timestamp() as i64, 0) + .single() + .ok_or(ZeroCopyError::InvalidOffset)?, + tcb_status: TcbStatus::try_from(tcb_level_view.tcb_status()) + .map_err(|_| ZeroCopyError::InvalidEnumValue)?, + advisory_ids: if advisory_ids_vec.is_empty() { + None + } else { + Some(advisory_ids_vec) + }, + }; + tcb_levels_vec.push(tcb_level_app); + } + } + + Ok(TcbInfo { + id: id_option, + version, + issue_date, + next_update, + fmspc, + pce_id, + tcb_type: view.tcb_type(), + tcb_evaluation_data_number: view.tcb_evaluation_data_number(), + tdx_module, + tdx_module_identities: tdx_module_identities_option, + tcb_levels: tcb_levels_vec, + }) +} diff --git a/src/types/pod/tcb_info/zero_copy/error.rs b/src/types/pod/tcb_info/zero_copy/error.rs new file mode 100644 index 0000000..57d5db4 --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/error.rs @@ -0,0 +1,52 @@ +// src/types/pod/tcb_info/zero_copy/error.rs + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ZeroCopyError { + InvalidSliceLength, + InvalidUtf8, + InvalidOffset, + DataNotPresent, + InvalidEnumValue, + AlignmentError, + OutputWouldHaveSlop, + PodCastError, + UnexpectedSgxComponentCount, + NoMatchingSgxTcbLevel, + MissingTdxComponentsInTcbInfo, +} + +impl ZeroCopyError { + // Helper function to create ZeroCopyError from bytemuck::PodCastError + // This should be pub(super) if cast_slice is in the same module, or pub if used more widely. + // Assuming it's used within the zero_copy module: + pub(super) fn from_bytemuck_error(e: bytemuck::PodCastError) -> Self { + match e { + bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => ZeroCopyError::AlignmentError, + bytemuck::PodCastError::OutputSliceWouldHaveSlop => ZeroCopyError::OutputWouldHaveSlop, + bytemuck::PodCastError::SizeMismatch => ZeroCopyError::InvalidSliceLength, // Or a more specific SizeMismatch variant + _ => ZeroCopyError::PodCastError, + } + } +} + +impl core::fmt::Display for ZeroCopyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let msg = match self { + ZeroCopyError::InvalidSliceLength => "Invalid slice length encountered during parsing", + ZeroCopyError::InvalidUtf8 => "Invalid UTF-8 sequence encountered", + ZeroCopyError::InvalidOffset => "Invalid offset calculation or out of bounds", + ZeroCopyError::DataNotPresent => "Expected data not present where indicated", + ZeroCopyError::InvalidEnumValue => "Invalid value for enum conversion", + ZeroCopyError::AlignmentError => "Input slice is not sufficiently aligned for the target type", + ZeroCopyError::OutputWouldHaveSlop => "Output slice would have uninitialized trailing padding bytes", + ZeroCopyError::PodCastError => "A general bytemuck PodCastError occurred", + ZeroCopyError::UnexpectedSgxComponentCount => "Unexpected number of SGX TCB components encountered", + ZeroCopyError::NoMatchingSgxTcbLevel => "No matching SGX TCB level found for the provided PCK extension", + ZeroCopyError::MissingTdxComponentsInTcbInfo => "TDX TCB components missing in TCB Info for a TDX quote", + }; + write!(f, "{}", msg) + } +} + +// Optionally, implement std::error::Error if this needs to interoperate with other error types +// impl std::error::Error for ZeroCopyError {} diff --git a/src/types/pod/tcb_info/zero_copy/iterators.rs b/src/types/pod/tcb_info/zero_copy/iterators.rs new file mode 100644 index 0000000..3ac1e34 --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/iterators.rs @@ -0,0 +1,367 @@ +// src/types/pod/tcb_info/zero_copy/iterators.rs + +use super::error::ZeroCopyError; +use super::structs::{ + TcbComponentZeroCopy, TcbLevelZeroCopy, TdxModuleIdentityZeroCopy, TdxTcbLevelZeroCopy, +}; +use crate::types::pod::tcb_info::{ + TcbComponentHeader, TcbLevelHeader, TdxModuleIdentityHeader, TdxTcbLevelHeader, +}; +use bytemuck::Pod; +use core::str; + +use core::mem; // For align_of + +// Helper from structs.rs - consider moving to a shared util if this pattern repeats more +#[inline] +fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { + bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) +} + +// --- Iterators --- + +// Iterator for Advisory IDs (string slices) +pub struct AdvisoryIdIter<'a> { + lengths_payload: &'a [u8], // Slice containing array of u16 lengths + data_payload: &'a [u8], // Slice containing concatenated string data + count: u32, + current_idx: u32, + current_data_offset: usize, +} + +impl<'a> AdvisoryIdIter<'a> { + pub(super) fn new(lengths_payload: &'a [u8], data_payload: &'a [u8], count: u32) -> Self { + Self { + lengths_payload, + data_payload, + count, + current_idx: 0, + current_data_offset: 0, + } + } +} + +impl<'a> Iterator for AdvisoryIdIter<'a> { + type Item = Result<&'a str, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + + let len_size = core::mem::size_of::(); + let len_offset = (self.current_idx as usize).checked_mul(len_size)?; + let len_bytes_end = len_offset.checked_add(len_size)?; + + if len_bytes_end > self.lengths_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let len_bytes_slice = self.lengths_payload.get(len_offset..len_bytes_end)?; + let len = u16::from_le_bytes(len_bytes_slice.try_into().ok()?) as usize; + + let data_end = self.current_data_offset.checked_add(len)?; + if data_end > self.data_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let data_slice = self.data_payload.get(self.current_data_offset..data_end)?; + let res = str::from_utf8(data_slice).map_err(|_| ZeroCopyError::InvalidUtf8); + + self.current_data_offset = data_end; + self.current_idx += 1; + Some(res) + } +} + +// Iterator for TdxTcbLevelZeroCopy +pub struct TdxTcbLevelIter<'a> { + full_payload: &'a [u8], // This is the payload for *all* TdxTcbLevels for a given identity + count: u32, + current_idx: u32, + current_offset: usize, +} + +impl<'a> TdxTcbLevelIter<'a> { + pub(super) fn new(full_payload: &'a [u8], count: u32) -> Self { + Self { + full_payload, + count, + current_idx: 0, + current_offset: 0, + } + } +} + +impl<'a> Iterator for TdxTcbLevelIter<'a> { + type Item = Result, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + + let header_size = core::mem::size_of::(); + let header_end = self.current_offset.checked_add(header_size)?; + if header_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let header_slice = &self.full_payload[self.current_offset..header_end]; + let header: &'a TdxTcbLevelHeader = match cast_slice(header_slice) { + Ok(h) => h, + Err(e) => return Some(Err(e)), + }; + + let current_item_internal_offset = header_end; + + let actual_item_payload_len = header + .advisory_ids_lengths_array_len + .checked_add(header.advisory_ids_data_total_len) + .ok_or(ZeroCopyError::InvalidOffset); + + let actual_item_payload_len = match actual_item_payload_len { + Ok(len) => len as usize, + Err(e) => return Some(Err(e)), + }; + + let item_payload_actual_end = + match current_item_internal_offset.checked_add(actual_item_payload_len) { + Some(end) => end, + None => return Some(Err(ZeroCopyError::InvalidOffset)), + }; + + if item_payload_actual_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let item_payload_slice = + &self.full_payload[current_item_internal_offset..item_payload_actual_end]; + + let view_result = TdxTcbLevelZeroCopy::new(header, item_payload_slice); + + self.current_offset = item_payload_actual_end; // End of actual data for this item + + // Align self.current_offset for the NEXT header + let align_to = mem::align_of::(); + self.current_offset = (self.current_offset + align_to - 1) & !(align_to - 1); + + self.current_idx += 1; + Some(view_result) + } +} + +// Iterator for TdxModuleIdentityZeroCopy +pub struct TdxModuleIdentityIter<'a> { + full_payload: &'a [u8], // Payload for *all* TdxModuleIdentities for a TcbInfo + count: u32, + current_idx: u32, + current_offset: usize, +} + +impl<'a> TdxModuleIdentityIter<'a> { + pub(super) fn new(full_payload: &'a [u8], count: u32) -> Self { + Self { + full_payload, + count, + current_idx: 0, + current_offset: 0, + } + } +} + +impl<'a> Iterator for TdxModuleIdentityIter<'a> { + type Item = Result, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + let header_size = core::mem::size_of::(); + let header_end = self.current_offset.checked_add(header_size)?; + if header_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let header_slice = &self.full_payload[self.current_offset..header_end]; + let header: &'a TdxModuleIdentityHeader = match cast_slice(header_slice) { + Ok(h) => h, + Err(e) => return Some(Err(e)), + }; + + let current_item_internal_offset = header_end; + + let alignment = mem::align_of::(); + let offset = header.id_len as usize + (alignment - 1) & !(alignment - 1); + let actual_item_payload_len = (offset) + .checked_add(header.tcb_levels_total_payload_len as usize) + .ok_or(ZeroCopyError::InvalidOffset); + + let actual_item_payload_len = match actual_item_payload_len { + Ok(len) => len, + Err(e) => return Some(Err(e)), + }; + + let item_payload_actual_end = + match current_item_internal_offset.checked_add(actual_item_payload_len) { + Some(end) => end, + None => return Some(Err(ZeroCopyError::InvalidOffset)), + }; + if item_payload_actual_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + let item_payload_slice = + &self.full_payload[current_item_internal_offset..item_payload_actual_end]; + + let view_result = TdxModuleIdentityZeroCopy::new(header, item_payload_slice); + + self.current_offset = item_payload_actual_end; // End of actual data for this item + + // Align self.current_offset for the NEXT header + let align_to = mem::align_of::(); + self.current_offset = (self.current_offset + align_to - 1) & !(align_to - 1); + + self.current_idx += 1; + Some(view_result) + } +} + +// Iterator for TcbComponentZeroCopy +pub struct TcbComponentIter<'a> { + component_headers: &'a [TcbComponentHeader; 16], // Array of headers + full_strings_payload: &'a [u8], // Concatenated strings for all 16 components + current_idx: usize, // Index into the headers array (0-15) + current_string_offset: usize, // Offset into full_strings_payload +} + +impl<'a> TcbComponentIter<'a> { + pub(super) fn new( + headers: &'a [TcbComponentHeader; 16], + full_strings_payload: &'a [u8], + ) -> Self { + Self { + component_headers: headers, + full_strings_payload, + current_idx: 0, + current_string_offset: 0, + } + } +} + +impl<'a> Iterator for TcbComponentIter<'a> { + type Item = Result, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= 16 { + return None; + } + let header = &self.component_headers[self.current_idx]; + let cat_len = header.category_len as usize; + let comp_type_len = header.component_type_len as usize; + let total_len_for_comp = cat_len.checked_add(comp_type_len)?; + + let string_data_end = self.current_string_offset.checked_add(total_len_for_comp)?; + if string_data_end > self.full_strings_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let component_string_slice = self + .full_strings_payload + .get(self.current_string_offset..string_data_end)?; + + let view_res = TcbComponentZeroCopy::new(header, component_string_slice); + + self.current_string_offset = string_data_end; + self.current_idx += 1; + Some(view_res) + } +} + +// Iterator for TcbLevelZeroCopy +pub struct TcbLevelIter<'a> { + full_payload: &'a [u8], // Payload for *all* TcbLevels for a TcbInfo + count: u32, + current_idx: u32, + current_offset: usize, +} + +impl<'a> TcbLevelIter<'a> { + pub(super) fn new(full_payload: &'a [u8], count: u32) -> Self { + Self { + full_payload, + count, + current_idx: 0, + current_offset: 0, + } + } +} + +impl<'a> Iterator for TcbLevelIter<'a> { + type Item = Result, ZeroCopyError>; + + fn next(&mut self) -> Option { + if self.current_idx >= self.count { + return None; + } + + let header_size = mem::size_of::(); + let header_slice_end = match self.current_offset.checked_add(header_size) { + Some(end) => end, + None => return Some(Err(ZeroCopyError::InvalidOffset)), + }; + + if header_slice_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + + let header_slice = &self.full_payload[self.current_offset..header_slice_end]; + let header: &'a TcbLevelHeader = match cast_slice(header_slice) { + Ok(h) => h, + Err(e) => return Some(Err(e)), + }; + + let current_item_internal_offset = header_slice_end; + + let sgx_strings_len = header.sgx_components_strings_total_len as usize; + let tdx_strings_len = if header.tdx_tcb_components_present == 1 { + header.tdx_components_strings_total_len as usize + } else { + 0 + }; + let adv_ids_lengths_len = header.advisory_ids_lengths_array_len as usize; + let adv_ids_data_len = header.advisory_ids_data_total_len as usize; + + let actual_item_payload_len = sgx_strings_len + .checked_add(tdx_strings_len) + .and_then(|sum| sum.checked_add(adv_ids_lengths_len)) + .and_then(|sum| sum.checked_add(adv_ids_data_len)); + + let actual_item_payload_len = match actual_item_payload_len { + Some(len) => len, + None => return Some(Err(ZeroCopyError::InvalidOffset)), + }; + + let item_payload_actual_end = + match current_item_internal_offset.checked_add(actual_item_payload_len) { + Some(end) => end, + None => return Some(Err(ZeroCopyError::InvalidOffset)), + }; + + if item_payload_actual_end > self.full_payload.len() { + return Some(Err(ZeroCopyError::InvalidSliceLength)); + } + let item_payload_slice = + &self.full_payload[current_item_internal_offset..item_payload_actual_end]; + + let view_result = TcbLevelZeroCopy::new(header, item_payload_slice); + + self.current_offset = item_payload_actual_end; // End of actual data for this item + + // Align self.current_offset for the NEXT header + let align_to = mem::align_of::(); + self.current_offset = (self.current_offset + align_to - 1) & !(align_to - 1); + + self.current_idx += 1; + Some(view_result) + } +} diff --git a/src/types/pod/tcb_info/zero_copy/mod.rs b/src/types/pod/tcb_info/zero_copy/mod.rs new file mode 100644 index 0000000..ecbbc12 --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/mod.rs @@ -0,0 +1,22 @@ +// src/types/pod/tcb_info/zero_copy/mod.rs + +pub mod error; +pub mod structs; +pub mod iterators; +pub mod utils; + +#[cfg(feature = "full")] +pub mod conversion; + +pub use error::ZeroCopyError; +pub use structs::{ + TcbInfoZeroCopy, + TdxModulePodDataZeroCopy, + TdxModuleIdentityZeroCopy, + TdxTcbLevelZeroCopy, + TcbLevelZeroCopy, + TcbComponentZeroCopy, + // Potentially export intermediate payload structs if they are part of the public API +}; +// Iterators might also be exported if users need to name their types explicitly. +// pub use iterators::*; diff --git a/src/types/pod/tcb_info/zero_copy/structs.rs b/src/types/pod/tcb_info/zero_copy/structs.rs new file mode 100644 index 0000000..f1591cb --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/structs.rs @@ -0,0 +1,323 @@ +// src/types/pod/tcb_info/zero_copy/structs.rs + +use super::error::ZeroCopyError; +use super::iterators::*; // Will define iterators later +use crate::types::pod::tcb_info::{ + TcbInfoHeader, TdxModulePodData, TdxModuleIdentityHeader, TdxTcbLevelHeader, + TcbLevelHeader, TcbComponentHeader +}; +use bytemuck::Pod; +use std::str::from_utf8; + +// --- Helper to cast slices --- +// It's good practice to have this in a shared utility if used in multiple places, +// but for now, keeping it here for self-containment of this module's direct needs. +#[inline] +fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { + bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) +} + +// --- Top-Level ZeroCopy Struct --- + +#[derive(Debug, Copy, Clone)] +pub struct TcbInfoZeroCopy<'a> { + pub header: &'a TcbInfoHeader, + tdx_module_data_payload: Option<&'a TdxModulePodData>, // Directly borrowed if present + tdx_module_identities_section_payload: &'a [u8], + tcb_levels_section_payload: &'a [u8], +} + +impl<'a> TcbInfoZeroCopy<'a> { + /// Creates a zero-copy view from a byte slice that starts with TcbInfoHeader + /// and is followed by its complete payload. + pub fn from_bytes(bytes: &'a [u8]) -> Result { + if bytes.len() < core::mem::size_of::() { + return Err(ZeroCopyError::InvalidSliceLength); + } + let (header_bytes, main_payload) = bytes.split_at(core::mem::size_of::()); + let header: &TcbInfoHeader = cast_slice(header_bytes)?; + + let mut current_offset = 0; + let tdx_module_data_payload = if header.tdx_module_present == 1 { + let len = header.tdx_module_data_len as usize; + let slice = main_payload.get(current_offset..current_offset + len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += len; + Some(cast_slice(slice)?) + } else { + None + }; + + let identities_len = header.tdx_module_identities_total_payload_len as usize; + let tdx_module_identities_section_payload = main_payload.get(current_offset..current_offset + identities_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += identities_len; + + let levels_len = header.tcb_levels_total_payload_len as usize; + let tcb_levels_section_payload = main_payload.get(current_offset..current_offset + levels_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += levels_len; + + if current_offset > main_payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + + Ok(Self { + header, + tdx_module_data_payload, + tdx_module_identities_section_payload, + tcb_levels_section_payload, + }) + } + + // --- Direct Header Accessors --- + pub fn id_type_bytes(&self) -> &'a [u8; 6] { &self.header.id_type } + pub fn version(&self) -> u32 { self.header.version } + pub fn issue_date_timestamp(&self) -> i64 { self.header.issue_date_timestamp } + pub fn next_update_timestamp(&self) -> i64 { self.header.next_update_timestamp } + // Parses the hex string and returns the byte array + pub fn fmspc(&self) -> [u8; 6] { + hex::decode(from_utf8(&self.header.fmspc_hex).unwrap()).unwrap().try_into().unwrap() + } + // Parses the hex string and returns the byte array + pub fn pce_id(&self) -> [u8; 2] { + hex::decode(from_utf8(&self.header.pce_id_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn tcb_type(&self) -> u8 { self.header.tcb_type } + pub fn tcb_evaluation_data_number(&self) -> u32 { self.header.tcb_evaluation_data_number } + + // --- Parsed/Structured Accessors --- + pub fn tdx_module(&self) -> Option> { + self.tdx_module_data_payload.map(TdxModulePodDataZeroCopy::new) + } + + pub fn tdx_module_identities_count(&self) -> u32 { self.header.tdx_module_identities_count } + pub fn tdx_module_identities(&self) -> TdxModuleIdentityIter<'a> { + TdxModuleIdentityIter::new( + self.tdx_module_identities_section_payload, + self.header.tdx_module_identities_count, + ) + } + + pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels(&self) -> TcbLevelIter<'a> { + TcbLevelIter::new( + self.tcb_levels_section_payload, + self.header.tcb_levels_count, + ) + } +} + +// --- Individual Component ZeroCopy Structs --- + +// TdxModulePodDataZeroCopy (simple wrapper for already Pod data) +#[derive(Debug, Copy, Clone)] +pub struct TdxModulePodDataZeroCopy<'a> { + pub data: &'a TdxModulePodData, +} +impl<'a> TdxModulePodDataZeroCopy<'a> { + pub fn new(data: &'a TdxModulePodData) -> Self { Self { data } } + pub fn mrsigner(&self) -> [u8; 48] { + hex::decode(from_utf8(&self.data.mrsigner_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn attributes(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.data.attributes_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn attributes_mask(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.data.attributes_mask_hex).unwrap()).unwrap().try_into().unwrap() + } +} + +// TcbComponentZeroCopy +#[derive(Debug, Copy, Clone)] +pub struct TcbComponentZeroCopy<'a> { + pub header: &'a TcbComponentHeader, + category_payload: &'a [u8], + component_type_payload: &'a [u8], +} + +impl<'a> TcbComponentZeroCopy<'a> { + // 'payload' is the combined string data for THIS component + pub fn new(header: &'a TcbComponentHeader, payload: &'a [u8]) -> Result { + let cat_len = header.category_len as usize; + let comp_type_len = header.component_type_len as usize; + if cat_len.checked_add(comp_type_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + Ok(Self { + header, + category_payload: &payload[..cat_len], + component_type_payload: &payload[cat_len .. cat_len + comp_type_len], + }) + } + pub fn cpusvn(&self) -> u8 { self.header.cpusvn } + pub fn category_str(&self) -> Result<&'a str, ZeroCopyError> { + from_utf8(self.category_payload).map_err(|_| ZeroCopyError::InvalidUtf8) + } + pub fn component_type_str(&self) -> Result<&'a str, ZeroCopyError> { + from_utf8(self.component_type_payload).map_err(|_| ZeroCopyError::InvalidUtf8) + } +} + +// TdxTcbLevelZeroCopy +#[derive(Debug, Copy, Clone)] +pub struct TdxTcbLevelZeroCopy<'a> { + header: &'a TdxTcbLevelHeader, + advisory_ids_lengths_payload: &'a [u8], + advisory_ids_data_payload: &'a [u8], +} + +impl<'a> TdxTcbLevelZeroCopy<'a> { + // 'payload' is specific to this TdxTcbLevel's advisory IDs (lengths array + data) + pub fn new(header: &'a TdxTcbLevelHeader, payload: &'a [u8]) -> Result { + let lengths_len = header.advisory_ids_lengths_array_len as usize; + let data_len = header.advisory_ids_data_total_len as usize; + if lengths_len.checked_add(data_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + Ok(Self { + header, + advisory_ids_lengths_payload: &payload[..lengths_len], + advisory_ids_data_payload: &payload[lengths_len .. lengths_len + data_len], + }) + } + pub fn tcb_isvsvn(&self) -> u8 { self.header.tcb_isvsvn } + pub fn tcb_status(&self) -> u8 { + self.header.tcb_status + } + pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } + pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { + AdvisoryIdIter::new( + self.advisory_ids_lengths_payload, + self.advisory_ids_data_payload, + self.header.advisory_ids_count, + ) + } +} + +// TdxModuleIdentityZeroCopy +#[derive(Debug, Copy, Clone)] +pub struct TdxModuleIdentityZeroCopy<'a> { + pub header: &'a TdxModuleIdentityHeader, + id_payload: &'a [u8], // Slice for the ID string + tcb_levels_section_payload: &'a [u8], // Slice for all TdxTcbLevels of this identity +} + +impl<'a> TdxModuleIdentityZeroCopy<'a> { + // 'payload' is specific to this TdxModuleIdentity (id_string + its TdxTcbLevels section) + pub fn new(header: &'a TdxModuleIdentityHeader, payload: &'a [u8]) -> Result { + let id_len = header.id_len as usize; + + let tcb_levels_len = header.tcb_levels_total_payload_len as usize; + + let tcb_levels_alignment = core::mem::align_of::(); + let offset = id_len + (tcb_levels_alignment - 1) & !(tcb_levels_alignment - 1); + + if offset.checked_add(tcb_levels_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } + + Ok(Self { + header, + id_payload: &payload[..id_len], + tcb_levels_section_payload: &payload[offset .. offset + tcb_levels_len], + }) + } + pub fn mrsigner(&self) -> [u8; 48] { + hex::decode(from_utf8(&self.header.mrsigner_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn attributes(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.header.attributes_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn attributes_mask(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.header.attributes_mask_hex).unwrap()).unwrap().try_into().unwrap() + } + pub fn id_str(&self) -> Result<&'a str, ZeroCopyError> { + from_utf8(self.id_payload).map_err(|_| ZeroCopyError::InvalidUtf8) + } + pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels(&self) -> TdxTcbLevelIter<'a> { + TdxTcbLevelIter::new(self.tcb_levels_section_payload, self.header.tcb_levels_count) + } +} + +// TcbLevelZeroCopy +#[derive(Debug, Copy, Clone)] +pub struct TcbLevelZeroCopy<'a> { + pub header: &'a TcbLevelHeader, + sgx_components_strings_payload: &'a [u8], + tdx_components_strings_payload: Option<&'a [u8]>, + advisory_ids_lengths_payload: &'a [u8], + advisory_ids_data_payload: &'a [u8], +} + +impl<'a> TcbLevelZeroCopy<'a> { + // 'payload' is specific to this TcbLevel (sgx_comp_strings | tdx_comp_strings (opt) | adv_id_lengths | adv_id_data) + pub fn new(header: &'a TcbLevelHeader, payload: &'a [u8]) -> Result { + let mut current_offset = 0; + + let sgx_len = header.sgx_components_strings_total_len as usize; + let sgx_payload = payload.get(current_offset..current_offset + sgx_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += sgx_len; + + let tdx_payload = if header.tdx_tcb_components_present == 1 { + let tdx_len = header.tdx_components_strings_total_len as usize; + let slice = payload.get(current_offset..current_offset + tdx_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += tdx_len; + Some(slice) + } else { + None + }; + + let adv_lengths_len = header.advisory_ids_lengths_array_len as usize; + let adv_lengths_payload = payload.get(current_offset..current_offset + adv_lengths_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += adv_lengths_len; + + let adv_data_len = header.advisory_ids_data_total_len as usize; + let adv_data_payload = payload.get(current_offset..current_offset + adv_data_len) + .ok_or(ZeroCopyError::InvalidSliceLength)?; + current_offset += adv_data_len; + + if current_offset > payload.len() { return Err(ZeroCopyError::InvalidSliceLength); } + + Ok(Self { + header, + sgx_components_strings_payload: sgx_payload, + tdx_components_strings_payload: tdx_payload, + advisory_ids_lengths_payload: adv_lengths_payload, + advisory_ids_data_payload: adv_data_payload, + }) + } + pub fn tcb_status(&self) -> u8 { + self.header.tcb_status + } + pub fn pce_svn(&self) -> u16 { self.header.pce_svn } + pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } + pub fn sgx_tcb_components(&self) -> TcbComponentIter<'a> { + TcbComponentIter::new( + &self.header.sgx_tcb_components, + self.sgx_components_strings_payload, + ) + } + pub fn tdx_tcb_components(&self) -> Option> { + if self.header.tdx_tcb_components_present == 1 { + self.tdx_components_strings_payload.map(|payload| { + TcbComponentIter::new(&self.header.tdx_tcb_components, payload) + }) + } else { + None + } + } + pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { + AdvisoryIdIter::new( + self.advisory_ids_lengths_payload, + self.advisory_ids_data_payload, + self.header.advisory_ids_count, + ) + } +} diff --git a/src/types/pod/tcb_info/zero_copy/utils/mod.rs b/src/types/pod/tcb_info/zero_copy/utils/mod.rs new file mode 100644 index 0000000..299149d --- /dev/null +++ b/src/types/pod/tcb_info/zero_copy/utils/mod.rs @@ -0,0 +1,162 @@ +use super::{ + error::ZeroCopyError, + structs::{TcbInfoZeroCopy, TcbLevelZeroCopy}, +}; +use crate::types::{ + quote::{Quote, QuoteBody}, + sgx_x509::SgxPckExtension, +}; + +pub fn lookup<'a>( + pck_extension: &SgxPckExtension, + tcb_info: &TcbInfoZeroCopy<'a>, + quote: &Quote, +) -> Result<(u8, u8, Vec), ZeroCopyError> { + let mut sgx_tcb_status_u8: u8 = 0; // Default, will be overwritten + let mut advisory_ids: Vec = Vec::new(); + let mut found_sgx_match_index: Option = None; + + for (idx, tcb_level_res) in tcb_info.tcb_levels().enumerate() { + let tcb_level = tcb_level_res?; + if pck_in_tcb_level_zc(&tcb_level, pck_extension)? { + sgx_tcb_status_u8 = tcb_level.tcb_status(); + advisory_ids = tcb_level + .advisory_ids() + .map(|res| res.map(|s| s.to_string())) + .collect::, ZeroCopyError>>()?; + found_sgx_match_index = Some(idx); + break; + } + } + + if found_sgx_match_index.is_none() { + // As per original TcbStatus::lookup, this is an error condition. + return Err(ZeroCopyError::NoMatchingSgxTcbLevel); + } + + let mut tdx_tcb_status_u8 = 7u8; // TcbStatus::Unspecified = 7 + + if let QuoteBody::Td10QuoteBody(tdx_quote_body) = "e.body { + // Start iterating from the found sgx matching level's index + for tdx_tcb_levels_res in tcb_info.tcb_levels().skip(found_sgx_match_index.unwrap()) { + let tdx_tcb_level = tdx_tcb_levels_res?; + if let Some(tdx_tcb_levels_iter) = tdx_tcb_level.tdx_tcb_components() { + let mut tdx_components_match = true; + // tdx_tcb_levels_iter yields 16 components + // tdx_quote_body.tee_tcb_svn is [u8; 16] + for (tdx_tcb_components_res, quote_svn_val) in + tdx_tcb_levels_iter.zip(tdx_quote_body.tee_tcb_svn.iter()) + { + let platform_comp = tdx_tcb_components_res?; + if platform_comp.cpusvn() > *quote_svn_val { + tdx_components_match = false; + break; + } + } + + if tdx_components_match { + tdx_tcb_status_u8 = tdx_tcb_level.tcb_status(); + advisory_ids = tdx_tcb_level + .advisory_ids() + .map(|res| res.map(|s| s.to_string())) + .collect::, ZeroCopyError>>()?; + break; // Found matching TDX TCB level + } + } else { + // This TCB level in TCB Info does not have TDX components, + // but we have a TDX quote. This is an inconsistency. + // Original code returns: anyhow!("did not find tdx tcb components in tcb info when Td10QuoteBody is provided for the quote") + return Err(ZeroCopyError::MissingTdxComponentsInTcbInfo); + } + } + // If loop finishes without break, tdx_tcb_status_u8 remains Unspecified if no match was found + // or updated if a match was found. This matches original logic where if no TDX match, status isn't further updated. + } + + Ok((sgx_tcb_status_u8, tdx_tcb_status_u8, advisory_ids)) +} + +fn pck_in_tcb_level_zc<'a>( + level: &TcbLevelZeroCopy<'a>, + pck_extension: &SgxPckExtension, +) -> Result { + let pck_compsvn = &pck_extension.tcb.compsvn; // This is [u8; 16] + + // sgx_tcb_components() yields an iterator for 16 components + for (idx, comp_res) in level.sgx_tcb_components().enumerate() { + let comp = comp_res?; + // The TcbComponentIter for sgx_tcb_components is expected to yield 16 components. + // pck_compsvn is also fixed at 16. + if idx < 16 { + if pck_compsvn[idx] < comp.cpusvn() { + return Ok(false); + } + } else { + // Should not happen if TcbComponentIter is correctly implemented for 16 components + return Err(ZeroCopyError::UnexpectedSgxComponentCount); + } + } + + // Check PCE SVN + if pck_extension.tcb.pcesvn < level.pce_svn() { + return Ok(false); + } + + Ok(true) +} + +#[cfg(all(test, not(feature = "zero-copy")))] +mod tests { + use crate::types::pod::tcb_info::serialize::{SerializedTcbInfo, serialize_tcb_pod}; + use crate::types::{ + pod::tcb_info::zero_copy::TcbInfoZeroCopy, + quote::Quote, + tcb_info::{TcbInfoAndSignature, TcbStatus}, + }; + + #[test] + pub fn test_zero_copy_tcb_lookup() { + let quote_bytes = include_bytes!("../../../../../../data/quote_tdx.bin"); + let tcb_info_json_bytes = + include_bytes!("../../../../../../data/tcb_info_v3_with_tdx_module.json"); + + // Parse the quote and extract the PCK SGX extension + let quote = Quote::read(&mut quote_bytes.as_slice()).unwrap(); + let pck_extension = quote.signature.get_pck_extension().unwrap(); + + // Parse TCB Info and convert to ZeroCopy + let tcb_info_json: TcbInfoAndSignature = + serde_json::from_slice(tcb_info_json_bytes).unwrap(); + let tcb_info = tcb_info_json.get_tcb_info().unwrap(); + let mut signature = [0u8; 64]; + signature.copy_from_slice(tcb_info_json.signature.as_slice()); + let serialized_tcb_info = SerializedTcbInfo::from_rust_tcb_info(&tcb_info).unwrap(); + let serialize_tcb_info_bytes = serialize_tcb_pod(&serialized_tcb_info, &signature); + let tcb_info_zero_copy = + TcbInfoZeroCopy::from_bytes(&serialize_tcb_info_bytes[64..]).unwrap(); + + // Perform the lookup + // Start with TcbStatus::lookup + let (sgx_tcb_status_expected, tdx_tcb_status_expected, advisory_ids_expected) = + TcbStatus::lookup(&pck_extension, &tcb_info, "e).unwrap(); + + // ZeroCopy lookup + let (sgx_tcb_status_zc, tdx_tcb_status_zc, advisory_ids_zc) = + super::lookup(&pck_extension, &tcb_info_zero_copy, "e).unwrap(); + + assert_eq!( + sgx_tcb_status_expected, + TcbStatus::try_from(sgx_tcb_status_zc).unwrap(), + "SGX TCB status mismatch" + ); + assert_eq!( + tdx_tcb_status_expected, + TcbStatus::try_from(tdx_tcb_status_zc).unwrap(), + "TDX TCB status mismatch" + ); + assert_eq!( + advisory_ids_expected, advisory_ids_zc, + "Advisory IDs mismatch" + ); + } +} diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 33537bd..ea0e228 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -1,4 +1,4 @@ -use std::time::SystemTime; +use std::{str::from_utf8, time::SystemTime}; use anyhow::{Context, bail}; use chrono::{DateTime, Utc}; @@ -6,8 +6,6 @@ use p256::ecdsa::VerifyingKey; use p256::ecdsa::signature::Verifier; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; -use borsh::{BorshDeserialize, BorshSerialize}; -use crate::utils::borsh_datetime_as_instant; use super::{quote::{Quote, QuoteBody}, report::Td10ReportBody, sgx_x509::SgxPckExtension}; @@ -77,17 +75,16 @@ impl TcbInfoAndSignature { /// version is V3. The V3 API includes advisoryIDs and changes the format of /// the TcbLevel -#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] -#[serde(try_from = "u16")] -#[borsh(use_discriminant = true)] +#[derive(Deserialize, Serialize, Copy, Clone, Debug, Eq, PartialEq)] +#[serde(try_from = "u32", into = "u32")] pub enum TcbInfoVersion { V2 = 2, V3 = 3, } -impl TryFrom for TcbInfoVersion { +impl TryFrom for TcbInfoVersion { type Error = &'static str; - fn try_from(value: u16) -> std::result::Result { + fn try_from(value: u32) -> std::result::Result { match value { 2 => Ok(TcbInfoVersion::V2), 3 => Ok(TcbInfoVersion::V3), @@ -96,22 +93,26 @@ impl TryFrom for TcbInfoVersion { } } -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +impl From for u32 { + fn from(value: TcbInfoVersion) -> Self { + value as u32 + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct TcbInfo { #[serde(skip_serializing_if = "Option::is_none", rename = "id")] pub id: Option, pub version: TcbInfoVersion, - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub issue_date: chrono::DateTime, - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub next_update: chrono::DateTime, - #[serde(with = "hex")] - pub fmspc: [u8; 6], - #[serde(with = "hex")] - pub pce_id: [u8; 2], - pub tcb_type: u16, - pub tcb_evaluation_data_number: u16, + pub fmspc: String, + pub pce_id: String, + pub tcb_type: u8, + pub tcb_evaluation_data_number: u32, #[serde(skip_serializing_if = "Option::is_none")] pub tdx_module: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -120,6 +121,20 @@ pub struct TcbInfo { } impl TcbInfo { + pub fn fmspc_bytes(&self) -> [u8; 6] { + hex::decode(from_utf8(self.fmspc.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + + pub fn pce_id_bytes(&self) -> [u8; 2] { + hex::decode(from_utf8(self.pce_id.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + pub fn verify_tdx_module(&self, quote_body: &Td10ReportBody) -> anyhow::Result { if self.tdx_module.is_none() { return Err(anyhow::anyhow!("no tdx module found in tcb info")); @@ -154,14 +169,18 @@ impl TcbInfo { (&tdx_module.mrsigner, &tdx_module.attributes) }; + // Convert mrsigner and attributes to the appropriate type + let mrsigner_bytes: [u8; 48] = hex::decode(mrsigner).unwrap().try_into().unwrap(); + let attributes_bytes: [u8; 8] = hex::decode(attributes).unwrap().try_into().unwrap(); + // Check for mismatches with a single validation - if mrsigner != "e_body.mr_signer_seam { + if mrsigner_bytes != quote_body.mr_signer_seam { return Err(anyhow::anyhow!( "mrsigner mismatch between tdx module identity and tdx quote body" )); } - if attributes != "e_body.seam_attributes { + if attributes_bytes != quote_body.seam_attributes { return Err(anyhow::anyhow!( "attributes mismatch between tdx module identity and tdx quote body" )); @@ -184,7 +203,7 @@ impl TcbInfo { ) -> TcbStatus { // Only adjust if TDX module is OutOfDate if tdx_module_status != TcbStatus::OutOfDate { - return tdx_module_status; + return platform_status; } match platform_status { @@ -223,37 +242,28 @@ impl TcbInfo { _ => platform_status, } } - - pub fn from_borsh_bytes(bytes: &[u8]) -> anyhow::Result { - borsh::from_slice::(bytes) - .map_err(|e| anyhow::anyhow!("Failed to parse TcbInfo: {}", e)) - } - - pub fn to_borsh_bytes(&self) -> anyhow::Result> { - borsh::to_vec(self) - .map_err(|e| anyhow::anyhow!("Failed to serialize TcbInfo: {}", e)) - } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TcbLevel { pub tcb: Tcb, - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] + pub tcb_date: chrono::DateTime, pub tcb_status: TcbStatus, #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] pub advisory_ids: Option>, } -#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] -#[borsh(use_discriminant = true)] +/// Enum definition as per: https://github.com/automata-network/automata-on-chain-pccs/blob/d93c4881f1b40930bc72be06008d1e1537004d2f/src/helpers/FmspcTcbHelper.sol#L78-L87 +#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)] +#[repr(u8)] pub enum TcbStatus { UpToDate, - OutOfDate, - ConfigurationNeeded, SWHardeningNeeded, ConfigurationAndSWHardeningNeeded, + ConfigurationNeeded, + OutOfDate, OutOfDateConfigurationNeeded, Revoked, Unspecified, @@ -274,10 +284,32 @@ impl std::fmt::Display for TcbStatus { } } +impl TryFrom for TcbStatus { + type Error = &'static str; + fn try_from(value: u8) -> std::result::Result { + match value { + 0 => Ok(TcbStatus::UpToDate), + 1 => Ok(TcbStatus::SWHardeningNeeded), + 2 => Ok(TcbStatus::ConfigurationAndSWHardeningNeeded), + 3 => Ok(TcbStatus::ConfigurationNeeded), + 4 => Ok(TcbStatus::OutOfDate), + 5 => Ok(TcbStatus::OutOfDateConfigurationNeeded), + 6 => Ok(TcbStatus::Revoked), + 7 => Ok(TcbStatus::Unspecified), + _ => Err("Unsupported TCB status"), + } + } +} + +impl From for u8 { + fn from(value: TcbStatus) -> Self { + value as u8 + } +} + /// Contains information identifying a TcbLevel. -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] #[serde(untagged)] -#[borsh(use_discriminant = true)] pub enum Tcb { V2(TcbV2), V3(TcbV3), @@ -292,39 +324,43 @@ impl Tcb { } } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] pub struct TcbV3 { - sgxtcbcomponents: [TcbComponentV3; 16], + pub sgxtcbcomponents: [TcbComponentV3; 16], + pub pcesvn: u16, #[serde(skip_serializing_if = "Option::is_none")] - tdxtcbcomponents: Option<[TcbComponentV3; 16]>, - pcesvn: u16, + pub tdxtcbcomponents: Option<[TcbComponentV3; 16]>, } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, Copy, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] pub struct TcbComponentV3 { - svn: u8, + pub svn: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, + #[serde(skip_serializing_if = "Option::is_none", rename = "type")] + pub component_type: Option } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] pub struct TcbV2 { - sgxtcbcomp01svn: u8, - sgxtcbcomp02svn: u8, - sgxtcbcomp03svn: u8, - sgxtcbcomp04svn: u8, - sgxtcbcomp05svn: u8, - sgxtcbcomp06svn: u8, - sgxtcbcomp07svn: u8, - sgxtcbcomp08svn: u8, - sgxtcbcomp09svn: u8, - sgxtcbcomp10svn: u8, - sgxtcbcomp11svn: u8, - sgxtcbcomp12svn: u8, - sgxtcbcomp13svn: u8, - sgxtcbcomp14svn: u8, - sgxtcbcomp15svn: u8, - sgxtcbcomp16svn: u8, - pcesvn: u16, + pub sgxtcbcomp01svn: u8, + pub sgxtcbcomp02svn: u8, + pub sgxtcbcomp03svn: u8, + pub sgxtcbcomp04svn: u8, + pub sgxtcbcomp05svn: u8, + pub sgxtcbcomp06svn: u8, + pub sgxtcbcomp07svn: u8, + pub sgxtcbcomp08svn: u8, + pub sgxtcbcomp09svn: u8, + pub sgxtcbcomp10svn: u8, + pub sgxtcbcomp11svn: u8, + pub sgxtcbcomp12svn: u8, + pub sgxtcbcomp13svn: u8, + pub sgxtcbcomp14svn: u8, + pub sgxtcbcomp15svn: u8, + pub sgxtcbcomp16svn: u8, + pub pcesvn: u16, } impl Tcb { @@ -355,52 +391,108 @@ impl Tcb { v2.sgxtcbcomp15svn, v2.sgxtcbcomp16svn, ], - Self::V3(v3) => v3.sgxtcbcomponents.map(|comp| comp.svn), + Self::V3(v3) => { + let mut result = [0u8; 16]; + for i in 0..16 { + result[i] = v3.sgxtcbcomponents[i].svn; + } + result + }, } } pub fn tdx_tcb_components(&self) -> Option<[u8; 16]> { match self { Self::V2(_) => None, - Self::V3(v3) => v3.tdxtcbcomponents.map(|components| components.map(|comp| comp.svn)), + Self::V3(v3) => { + v3.tdxtcbcomponents.as_ref().map(|components| { + let mut result = [0u8; 16]; + for i in 0..16 { + result[i] = components[i].svn; + } + result + }) + }, } } } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct TdxModule { - #[serde(with = "hex", rename = "mrsigner")] - mrsigner: [u8; 48], - #[serde(with = "hex")] - attributes: [u8; 8], - #[serde(with = "hex")] - attributes_mask: [u8; 8], + #[serde(rename = "mrsigner")] + pub mrsigner: String, + pub attributes: String, + pub attributes_mask: String, +} + +impl TdxModule { + pub fn mrsigner_bytes(&self) -> [u8; 48] { + hex::decode(from_utf8(self.mrsigner.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + + pub fn attributes_bytes(&self) -> [u8; 8] { + hex::decode(from_utf8(self.attributes.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + + pub fn attributes_mask_bytes(&self) -> [u8; 8] { + hex::decode(from_utf8(self.attributes_mask.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct TdxModuleIdentity { #[serde(rename = "id")] - id: String, - #[serde(with = "hex", rename = "mrsigner")] - mrsigner: [u8; 48], - #[serde(with = "hex")] - attributes: [u8; 8], - #[serde(with = "hex")] - attributes_mask: [u8; 8], - tcb_levels: Vec, + pub id: String, + #[serde(rename = "mrsigner")] + pub mrsigner: String, + pub attributes: String, + pub attributes_mask: String, + pub tcb_levels: Vec, +} + +impl TdxModuleIdentity { + pub fn mrsigner_bytes(&self) -> [u8; 48] { + hex::decode(from_utf8(self.mrsigner.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + + pub fn attributes_bytes(&self) -> [u8; 8] { + hex::decode(from_utf8(self.attributes.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + + pub fn attributes_mask_bytes(&self) -> [u8; 8] { + hex::decode(from_utf8(self.attributes_mask.as_bytes()).unwrap()) + .unwrap() + .try_into() + .unwrap() + } } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct TdxTcbLevel { - tcb: TcbTdx, - #[borsh(deserialize_with = "borsh_datetime_as_instant::deserialize", serialize_with = "borsh_datetime_as_instant::serialize")] - tcb_date: chrono::DateTime, - tcb_status: TcbStatus, + pub tcb: TcbTdx, + + pub tcb_date: chrono::DateTime, + pub tcb_status: TcbStatus, #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] - advisory_ids: Option>, + pub advisory_ids: Option>, } impl TdxTcbLevel { @@ -409,9 +501,9 @@ impl TdxTcbLevel { } } -#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] pub struct TcbTdx { - isvsvn: u8, + pub isvsvn: u8, } impl TcbStatus { @@ -458,7 +550,7 @@ impl TcbStatus { let components_match = tdx_tcb_components .iter() .zip(body.tee_tcb_svn.iter()) - .all(|(&comp, &svn)| comp >= svn); + .all(|(&comp, &svn)| comp <= svn); if components_match { tdx_tcb_status = level.tcb_status; @@ -488,108 +580,4 @@ impl TcbStatus { .all(|(&pck, tcb)| pck >= tcb) && pck_extension.tcb.pcesvn >= level.tcb.pcesvn() } - -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parsing_tcb_info_without_tdx_module() { - let json = include_str!("../../data/tcb_info_v2.json"); - let tcb_info_and_signature: TcbInfoAndSignature = serde_json::from_str(json).unwrap(); - let tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); - - assert_eq!(tcb_info.tdx_module.is_none(), true); - } - - #[test] - fn test_parsing_tcb_info_with_tdx_module() { - let json = include_str!("../../data/tcb_info_v3_with_tdx_module.json"); - let tcb_info_and_signature: TcbInfoAndSignature = serde_json::from_str(json).unwrap(); - let original_tcb_info = tcb_info_and_signature.get_tcb_info().unwrap(); - assert_eq!(original_tcb_info.tdx_module.is_some(), true); - - // Serialize and Deserialize the TcbInfo - let tcb_info_borsh = borsh::to_vec(&original_tcb_info).unwrap(); - let tcb_info_deserialized: TcbInfo = borsh::from_slice(&tcb_info_borsh).unwrap(); - - // 3. Verify that deserialized matches original - assert_eq!(original_tcb_info.version, tcb_info_deserialized.version); - assert_eq!(original_tcb_info.issue_date, tcb_info_deserialized.issue_date); - assert_eq!(original_tcb_info.next_update, tcb_info_deserialized.next_update); - assert_eq!(original_tcb_info.fmspc, tcb_info_deserialized.fmspc); - assert_eq!(original_tcb_info.pce_id, tcb_info_deserialized.pce_id); - assert_eq!(original_tcb_info.tcb_type, tcb_info_deserialized.tcb_type); - - if let Some(original_tdx) = &original_tcb_info.tdx_module { - let deserialized_tdx = tcb_info_deserialized.tdx_module.as_ref().unwrap(); - assert_eq!(original_tdx.mrsigner, deserialized_tdx.mrsigner); - assert_eq!(original_tdx.attributes, deserialized_tdx.attributes); - assert_eq!(original_tdx.attributes_mask, deserialized_tdx.attributes_mask); - } - - // 5. Test TcbLevels - assert_eq!(original_tcb_info.tcb_levels.len(), tcb_info_deserialized.tcb_levels.len()); - - // Test the first TcbLevel in detail - let original_level = &original_tcb_info.tcb_levels[0]; - let deserialized_level = &tcb_info_deserialized.tcb_levels[0]; - - assert_eq!(original_level.tcb_date, deserialized_level.tcb_date); - assert_eq!(original_level.tcb_status, deserialized_level.tcb_status); - - // Test TcbLevel.tcb - match (&original_level.tcb, &deserialized_level.tcb) { - (Tcb::V2(original_v2), Tcb::V2(deserialized_v2)) => { - assert_eq!(original_v2.pcesvn, deserialized_v2.pcesvn); - assert_eq!(original_v2.sgxtcbcomp01svn, deserialized_v2.sgxtcbcomp01svn); - // Add more component checks as needed - }, - (Tcb::V3(original_v3), Tcb::V3(deserialized_v3)) => { - assert_eq!(original_v3.pcesvn, deserialized_v3.pcesvn); - assert_eq!(original_v3.sgxtcbcomponents.len(), deserialized_v3.sgxtcbcomponents.len()); - - // Check if tdxtcbcomponents exist and match - if let Some(original_tdx_comps) = &original_v3.tdxtcbcomponents { - let deserialized_tdx_comps = deserialized_v3.tdxtcbcomponents.as_ref().unwrap(); - assert_eq!(original_tdx_comps.len(), deserialized_tdx_comps.len()); - for (i, comp) in original_tdx_comps.iter().enumerate() { - assert_eq!(comp.svn, deserialized_tdx_comps[i].svn); - } - } - }, - _ => panic!("Tcb variant mismatch after deserialization"), - } - - // Test TdxModuleIdentities if present - if let Some(original_identities) = &original_tcb_info.tdx_module_identities { - let deserialized_identities = tcb_info_deserialized.tdx_module_identities.as_ref().unwrap(); - assert_eq!(original_identities.len(), deserialized_identities.len()); - - // Test the first TdxModuleIdentity - let original_identity = &original_identities[0]; - let deserialized_identity = &deserialized_identities[0]; - - assert_eq!(original_identity.id, deserialized_identity.id); - assert_eq!(original_identity.mrsigner, deserialized_identity.mrsigner); - assert_eq!(original_identity.attributes, deserialized_identity.attributes); - assert_eq!(original_identity.attributes_mask, deserialized_identity.attributes_mask); - - // Test TcbLevels in TdxModuleIdentity - assert_eq!( - original_identity.tcb_levels.len(), - deserialized_identity.tcb_levels.len() - ); - - if !original_identity.tcb_levels.is_empty() { - let original_tdx_level = &original_identity.tcb_levels[0]; - let deserialized_tdx_level = &deserialized_identity.tcb_levels[0]; - - assert_eq!(original_tdx_level.tcb.isvsvn, deserialized_tdx_level.tcb.isvsvn); - assert_eq!(original_tdx_level.tcb_date, deserialized_tdx_level.tcb_date); - assert_eq!(original_tdx_level.tcb_status, deserialized_tdx_level.tcb_status); - } - } - } } diff --git a/src/utils.rs b/src/utils.rs index 5631bdc..eed4813 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,59 +2,6 @@ use std::time::SystemTime; use x509_cert::{certificate::CertificateInner, crl::CertificateList}; -pub mod borsh_datetime_as_instant { - use borsh::{BorshDeserialize, BorshSerialize}; - use chrono::{DateTime, TimeZone, Utc}; - - pub fn serialize( - datetime: &DateTime, - writer: &mut W, - ) -> std::io::Result<()> { - // Convert DateTime to instant (seconds since UNIX epoch) - let secs = datetime.timestamp(); - let nanos = datetime.timestamp_subsec_nanos(); - - // Serialize as i64 (seconds) and u32 (nanos) - BorshSerialize::serialize(&secs, writer)?; - BorshSerialize::serialize(&nanos, writer)?; - Ok(()) - } - - pub fn deserialize(reader: &mut R) -> std::io::Result> { - // Deserialize seconds and nanos - let secs = i64::deserialize_reader(reader)?; - let nanos = u32::deserialize_reader(reader)?; - - // Reconstruct DateTime from seconds and nanos - Utc.timestamp_opt(secs, nanos).single() - .ok_or_else(|| std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Invalid datetime value" - )) - } -} - -// Module for serializing and deserializing UInt32LE with Borsh -pub mod borsh_uint32le { - use zerocopy::AsBytes; - - use crate::types::UInt32LE; - - pub fn serialize( - value: &UInt32LE, - writer: &mut W, - ) -> std::io::Result<()> { - // Use AsBytes to get the raw byte representation - writer.write_all(value.as_bytes()) - } - - pub fn deserialize(reader: &mut R) -> std::io::Result { - let mut bytes = [0u8; 4]; - reader.read_exact(&mut bytes)?; - Ok(UInt32LE::new(u32::from_le_bytes(bytes))) - } -} - /// A module for serializing and deserializing certificate chains. pub mod cert_chain { use serde::{Deserialize, de, ser}; @@ -304,4 +251,4 @@ pub fn read_bytes<'a>(bytes: &mut &'a [u8], size: usize) -> &'a [u8] { let (front, rest) = bytes.split_at(size); *bytes = rest; front -} +} \ No newline at end of file From a1a0f7fcb76c84c9486e5d59fe2377dcdbf18253 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Fri, 4 Jul 2025 14:52:19 +0800 Subject: [PATCH 08/14] Quote V5 (#21) * added td15report parser and modified quote struct to accommodate v5 * td1.5 relaunch check * added e2e v5 test * modified attestation signed data for v5 * tcb lookup for td1.5 * collateral struct constructor * enclave identity tcb eval number encoded as u32 * content hash * added alloy-sol-type and implemented vec conversion to get output bytes * x509 cert and crl hasher * verified output from bytes todo * separated test modules * change alloy-sol-types version requirement * replace tee_type with quote_body_type in VerifiedOutput * content hash error handling * separated out /tests into its own directory * write quote body type to output in big endian * im dumb af * tdx relaunch check update * missing tcb evaluation data number in tcbinfo content hash preimage * quote header version check * changed padding ordering --- Cargo.toml | 3 +- data/v5/alibaba_quote_5.dat | Bin 0 -> 5006 bytes data/v5/qe_td.json | 1 + data/v5/signing_cert.pem | 32 +++ data/v5/tcbinfov3_90C06F000000.json | 1 + src/lib.rs | 143 +++------- src/tdx.rs | 146 +++++++++++ src/types/collateral.rs | 70 ++++- src/types/enclave_identity.rs | 39 ++- src/types/mod.rs | 91 ++++++- src/types/pod/enclave_identity/mod.rs | 12 +- .../pod/enclave_identity/zero_copy/structs.rs | 2 +- src/types/quote/body.rs | 6 +- src/types/quote/header.rs | 7 +- src/types/quote/mod.rs | 104 +++++--- src/types/quote/signature.rs | 10 +- src/types/report.rs | 28 +- src/types/tcb_info.rs | 244 +++++++++--------- src/utils.rs | 15 +- tests/common/mod.rs | 81 ++++++ tests/tests.rs | 47 ++++ 21 files changed, 773 insertions(+), 309 deletions(-) create mode 100644 data/v5/alibaba_quote_5.dat create mode 100644 data/v5/qe_td.json create mode 100644 data/v5/signing_cert.pem create mode 100644 data/v5/tcbinfov3_90C06F000000.json create mode 100644 src/tdx.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/tests.rs diff --git a/Cargo.toml b/Cargo.toml index aee28e9..a8108d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,9 @@ pem = { version = "3.0.4" } base64ct = { version = "=1.6.0" } p256 = { version = "0.13.2" } sha2 = { version = "0.10.8" } -sha3 = { version = "0.10.8" } +tiny-keccak = { version = "2.0.2", features = ["keccak"] } bytemuck = { version = "1.23.0", features = ["derive"] } +alloy-sol-types = { version = "^1.1.0" } [dependencies.x509-verify] version = "0.4.6" diff --git a/data/v5/alibaba_quote_5.dat b/data/v5/alibaba_quote_5.dat new file mode 100644 index 0000000000000000000000000000000000000000..10758d58903669dad29b4a118c346d4801a18540 GIT binary patch literal 5006 zcmcIn3zQSp8D7D~brcX`iy{cpdK4DPb02dQ>%B9R$z(Ih&Lfjd)V9eaJ9$iAn@uJe zJ&N^F6sd5Ow^$S^R*J`>)>aOB#41Qde6~ofkE4LvN~;#3YPENgU5LwC0Z-?gY;yj4 z|M?&H`|kH|bJK{X>zeA%?JN2m2k-R$>JDu5LyNB+b;|Bp*Vr{_$-EV*)We7F?Vf1v z8rd{&L{n4qN$5|*Gx?djjYpTifA8&|Z57gcC!e|h+_CF7+;`FG%kOG=ZvV{tGW)J- z$sY(ko!|G>o+~r-XD2tf9`DaHOox4NRL!qdPpq4ivK^4luKZ!g-uT>A)0busys=>h zfBnj2fv@xW(K&VTgc;|=!z-oN9M&gVaQb>Z@Dr*0qp@~u~` z8$BX@)7-u+4ig)eKKrL>n|3unG-3WnP0qD1HGlG-DeCtpEM4;YWMakk={*~_+WIV? zPj5bX=V@L0cQ@}X-T&5`k92%~XYm>D%bS;Nm%=@e*^v=<<_~pZ7%SmcP%Oe&g(=vxKX!J>Gh+e`D9`{r!O@e|aqu zW!L>==j8cg+#QUJYgd%NTJpGe(w#qeZ2q#b=M~?({?+!G9nG#M$N&7`75l8)h{YpFS{a=lskr_m>Ok=v^%jefq~& zR*!zg2#=ie974m*t1PqMjjvq$yC-0;)_MLdmsPr29!oD;)%wxmmn_$`j`{c7cduB< zZ(d0Is9QBW4KM$2`pPT%KOMg(>AZCPfkRXNx@ex^8kc`?=hVI9rfs=l;W-aJwRvRd z-Akve8XLT)ICbTpH}2guan16}Z~g6|(NpP`C5yh8KlRtIzI`Q!zwivXcGL@_np`N0 z5hKo;H1eyaIxLT&jjaQF_6>aRS+nkuUDhp&FJ2~iHF?b%|Le~89S0BGd>eM-&Xya} z+rRCVuirKE>f9Z-O{9I!bzcx4jvRY+tMbufo8MF}+ctZQc;D0C=Ko#SM;v?U!-nd9 z4XpRSpRlcdXU(IVR&UvR3v=yL&5QoF_TdtFM(d6dbBA?mF#dDTm@zND$BySAotzpfVC*G@7QYq^GUhWe;@3vW_&K25efF z5`GrsXo4uv0_(&fU?GU^IRC0PR0 z-UxI(ETCxNpsoh2F`02eQm8^Q9P*&d!g<;5vW7(}!a{W*16Ah-9XLB)RcO2_hxmNd z?yR^-Ak?!`SiVgTDkR(;_Qb?pz z{vM8jP*n$hvk>Y&0syWTi?|(CDd(!#qrf7#nJ%Y=iAXU;Dn->eF6F(Q)oj0`)KT@= zEeR!64a7Xq?efvpSUVK`9WaI!EAG5b;#H%I2VPHBbX6l=IVVGsB!T*?>TVsK1aD2D z@VYAp0$LD{8BuH08P|ZIWCIvZ_V|IMcpVz2N9vn3aU9*hZ%mV=oB3H;`9xw$R~db=nSB>Vf$Ln+p3#UMPiosiYANwr5qe-(_~GfF;sNx{b0z zlA{R3Oc*c#2h}1v?<01AFaT1+*i{B%!!DS#9>xKcag-f1BSSO7Fp(jO08j^pW(3Uu zWZnL?5(pFl4&y+W0R|izOCxF}47bn_q0xafOBk}hRs#7A5lFLyAr5BAWJ7?jl|T&> z!E~Td08#|KLwiA~9EYJxlmIFV0SJJ@Pc3S$@hC}j0sv(bFwefBEDD1l!s&FE>8q2v z+})iEGl_D-2RJK)T+Td&_z))=LRiQa{7!pZv0{Scpa=(P(|WZ8OL0SxsOY|<@!&=X zg_eP1Z|s2^s7DqW(sTp8X74_7BXJ~9eGDL>3j;|=%f58FzfUk2R|p!twra1}u!z+Z zsSufbN65l@JH_rC�=-BmheiqT;fO_F`{O(57P+kH534OJfx|UUcyBG>HqIe3vNt zAyS>1cu_v`XhEy3e6!dQTFNA@{fO9kq;g2H<8+%l&rw;At%oxV)fFP}_Q26%N2?9u z#j=OBt1fmz)Q(%xB6sZMf$_08WP3ay9-41b=Zmsx9U}IBMg(U+zE5Zn}8LaVj z+n`KibuBu6ogOR|L-n1IP7lg7c9c%L(mJKfZWH82%1QuEW(IyE&L||O0?H>qOJ~2` zriBDQW=EHHzM^+J;~v;)r>Z2a2HX2WM1RHJFNxklN1{93VYCI3e3-}<_|AOMUsemQ zxKPnDqUO=CxR^=WgRt=43(pX~QYLfHnQ zenFx8NGQ~r=;pg9!GIo?imQACl8Lk{7jyHvV&uwRyH8WHd_J7fQfLs+lxn+G$w~$q zjM56yGAxD`!XdU4sgD^Y*^c*nt##ouM~*-SFUJs&ycy)20-G=saN5P*F-Jt$JIs zLz3yBBjB>w?WIU4Zk0V`$`*)YSk~oQShkR-|+v8K|%> zb2#PNS&=c!k55e7{8COI%p5bv-4jWDFmvp9=IX}O>z9TsVzs&Y82CzbWGS5`89D4v zS&}`$B4I?R?qnxs^C$g+mv<$CbSj#KlBZY7re$Zz!KBipEPJhN#qZ1%id8wK!H6bG za>dnSFM42iyP4_kcTrc7I8(ssv1)zp p99wJ)<=S(WVyMH9bHR$62jy^p_Q!axQz;ktP^7@j`5&BZ`Y&;D-`D^E literal 0 HcmV?d00001 diff --git a/data/v5/qe_td.json b/data/v5/qe_td.json new file mode 100644 index 0000000..e052914 --- /dev/null +++ b/data/v5/qe_td.json @@ -0,0 +1 @@ +{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2025-06-04T09:40:18Z","nextUpdate":"2025-07-04T09:40:18Z","tcbEvaluationDataNumber":17,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"182c2576c96697f002cc71f062cfd3f895cb39dc767050432600c2a095e576854c86ba4c41f7f36ead28142be9f6bf358d21d69463a8a720ee62785509c537c2"} \ No newline at end of file diff --git a/data/v5/signing_cert.pem b/data/v5/signing_cert.pem new file mode 100644 index 0000000..79a4518 --- /dev/null +++ b/data/v5/signing_cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG +A1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw +b3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD +VQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv +P+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju +ypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f +BEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz +LmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK +QEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG +SM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh +AKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg +AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/data/v5/tcbinfov3_90C06F000000.json b/data/v5/tcbinfov3_90C06F000000.json new file mode 100644 index 0000000..21df81e --- /dev/null +++ b/data/v5/tcbinfov3_90C06F000000.json @@ -0,0 +1 @@ +{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2025-06-05T03:43:45Z","nextUpdate":"2025-07-05T03:43:45Z","fmspc":"90C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":17,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":3},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate"}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":5,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":2,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":5,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":2,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00837"]}]},"signature":"a4c32be31be23ed7bd805429d80bb4ca597987e4ad0beac18172d4cc677f972d803c8696d667164a4a2e41bc29cfd980df0ad444f978a34b62b08d37b8495f7b"} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c26a63d..f449114 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod tdx; pub mod trust_store; pub mod types; pub mod utils; @@ -11,6 +12,8 @@ use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; #[cfg(feature = "full")] use std::time::SystemTime; #[cfg(feature = "full")] +use tdx::*; +#[cfg(feature = "full")] use trust_store::{TrustStore, TrustedIdentity}; #[cfg(feature = "full")] use types::{ @@ -44,11 +47,17 @@ pub fn verify_dcap_quote( ) -> anyhow::Result { // 1. Verify the integrity of the signature chain from the Quote to the Intel-issued PCK // certificate, and that no keys in the chain have been revoked. + use crate::types::quote::QuoteBody; let tcb_info = verify_integrity(current_time, &collateral, "e)?; // 2. Verify the Quoting Enclave source and all signatures in the Quote. let qe_tcb_status = verify_quote(current_time, &collateral, "e)?; + assert!( + qe_tcb_status != QeTcbStatus::Revoked, + "Quoting Enclave TCB Revoked" + ); + // 3. Verify the status of Intel SGX TCB described in the chain. let pck_extension = quote.signature.get_pck_extension()?; let (sgx_tcb_status, tdx_tcb_status, advisory_ids) = @@ -69,9 +78,28 @@ pub fn verify_dcap_quote( let mut tcb_status; if quote.header.tee_type == TDX_TEE_TYPE { tcb_status = tdx_tcb_status; - let tdx_module_status = - tcb_info.verify_tdx_module(quote.body.as_tdx_report_body().unwrap())?; - tcb_status = TcbInfo::converge_tcb_status_with_tdx_module(tcb_status, tdx_module_status); + let tdx_module_tcb_status = + verify_tdx_module(&tcb_info, quote.body.as_tdx_report_body().unwrap())?; + tcb_status = + TcbInfo::converge_tcb_status_with_tdx_module(tcb_status, tdx_module_tcb_status); + + if let QuoteBody::Td15QuoteBody(td_report) = "e.body { + let (relaunch_needed, configuration_needed) = check_for_relaunch( + &tcb_info, + td_report, + qe_tcb_status, + sgx_tcb_status, + tdx_tcb_status, + tdx_module_tcb_status, + ); + if relaunch_needed { + if configuration_needed { + tcb_status = TcbStatus::RelaunchAdvisedConfigurationNeeded; + } else { + tcb_status = TcbStatus::RelaunchAdvised; + } + } + } } else { tcb_status = sgx_tcb_status; } @@ -81,7 +109,7 @@ pub fn verify_dcap_quote( Ok(VerifiedOutput { quote_version: quote.header.version.get(), - tee_type: quote.header.tee_type.to_le(), // Compatible with VerifiedOutput defined on-chain + quote_body_type: quote.body_type, tcb_status: tcb_status as u8, fmspc: pck_extension.fmspc, quote_body: quote.body, @@ -325,6 +353,11 @@ pub fn verify_quote_signatures(quote: &Quote) -> anyhow::Result<()> { let body_bytes = quote.body.as_bytes(); let mut data = Vec::with_capacity(header_bytes.len() + body_bytes.len()); data.extend_from_slice(header_bytes); + if quote.header.version.get() > 4 { + // For version 5 and above, we include the quote body type and size + data.extend_from_slice("e.body_type.to_le_bytes()); + data.extend_from_slice("e.body_size.to_le_bytes()); + } data.extend_from_slice(body_bytes); let sig = Signature::from_slice(quote.signature.isv_signature)?; @@ -367,105 +400,3 @@ pub fn verify_tcb_status( TcbStatus::lookup(pck_extension, tcb_info, quote) } - -#[cfg(all(test, not(feature = "zero-copy")))] -mod tests { - - use std::time::Duration; - - use x509_cert::{crl::CertificateList, der::Decode}; - - use crate::types::tcb_info::TcbInfoAndSignature; - use crate::{ - types::enclave_identity::QuotingEnclaveIdentityAndSignature, utils::cert_chain_processor, - }; - - use super::*; - - fn sgx_quote_data() -> (Collateral, Quote<'static>) { - let collateral = include_str!("../data/full_collateral_sgx.json"); - let collateral: Collateral = serde_json::from_str(collateral).unwrap(); - let quote = include_bytes!("../data/quote_sgx.bin"); - let quote = Quote::read(&mut quote.as_slice()).unwrap(); - (collateral, quote) - } - - fn tdx_quote_data() -> (Collateral, Quote<'static>) { - let quote = include_bytes!("../data/quote_tdx.bin"); - let quote = Quote::read(&mut quote.as_slice()).unwrap(); - - let tcb_info_and_qe_identity_issuer_chain = include_bytes!("../data/signing_cert.pem"); - let tcb_info_and_qe_identity_issuer_chain = - cert_chain_processor::load_pem_chain_bpf_friendly( - tcb_info_and_qe_identity_issuer_chain, - ) - .unwrap(); - - let root_ca_crl = include_bytes!("../data/intel_root_ca_crl.der"); - let root_ca_crl = CertificateList::from_der(root_ca_crl).unwrap(); - - let tcb_info = include_bytes!("../data/tcb_info_v3_with_tdx_module.json"); - let tcb_info: TcbInfoAndSignature = serde_json::from_slice(tcb_info).unwrap(); - - let qe_identity = include_bytes!("../data/qeidentityv2_apiv4.json"); - let qe_identity: QuotingEnclaveIdentityAndSignature = - serde_json::from_slice(qe_identity).unwrap(); - - let platform_ca_crl = include_bytes!("../data/pck_platform_crl.der"); - let platform_ca_crl = CertificateList::from_der(platform_ca_crl).unwrap(); - - let collateral = Collateral { - tcb_info_and_qe_identity_issuer_chain, - root_ca_crl, - pck_crl: platform_ca_crl, - tcb_info, - qe_identity, - }; - (collateral, quote) - } - - fn test_sgx_time() -> SystemTime { - // Aug 29th 4:20pm, ~24 hours after quote was generated - SystemTime::UNIX_EPOCH + Duration::from_secs(1724962800) - } - - fn test_tdx_time() -> SystemTime { - // Pinned September 10th, 2024, 6:49am GMT - SystemTime::UNIX_EPOCH + Duration::from_secs(1725950994) - } - - #[test] - fn parse_tdx_quote() { - let bytes = include_bytes!("../data/quote_tdx.bin"); - let quote = Quote::read(&mut bytes.as_slice()).unwrap(); - println!("{:?}", quote); - } - - #[test] - fn parse_sgx_quote() { - let bytes = include_bytes!("../data/quote_sgx.bin"); - let quote = Quote::read(&mut bytes.as_slice()).unwrap(); - println!("{:?}", quote); - } - - #[test] - fn verify_integrity() { - let (collateral, quote) = sgx_quote_data(); - super::verify_integrity(test_sgx_time(), &collateral, "e) - .expect("certificate chain integrity should succeed"); - } - - #[test] - fn e2e_sgx_quote() { - let (collateral, quote) = sgx_quote_data(); - super::verify_dcap_quote(test_sgx_time(), collateral, quote) - .expect("certificate chain integrity should succeed"); - } - - #[test] - fn e2e_tdx_quote() { - let (collateral, quote) = tdx_quote_data(); - super::verify_dcap_quote(test_tdx_time(), collateral, quote) - .expect("certificate chain integrity should succeed"); - } -} diff --git a/src/tdx.rs b/src/tdx.rs new file mode 100644 index 0000000..010a520 --- /dev/null +++ b/src/tdx.rs @@ -0,0 +1,146 @@ +use crate::types::enclave_identity::QeTcbStatus; +use crate::types::report::{Td10ReportBody, Td15ReportBody}; +use crate::types::tcb_info::{TcbInfo, TcbStatus, TdxModuleIdentity}; + +pub fn verify_tdx_module( + tcb_info: &TcbInfo, + td_report: &Td10ReportBody, +) -> anyhow::Result { + if tcb_info.tdx_module.is_none() { + return Err(anyhow::anyhow!("no tdx module found in tcb info")); + } + + if tcb_info.tdx_module_identities.is_none() { + return Err(anyhow::anyhow!( + "no tdx module identities found in tcb info" + )); + } + + let (tdx_module_isv_svn, tdx_module_version) = + (td_report.tee_tcb_svn[0], td_report.tee_tcb_svn[1]); + + let tdx_module_identity = + find_tdx_module_identity(tdx_module_version, tcb_info).ok_or(anyhow::anyhow!( + "no tdx module identity found for version {}", + tdx_module_version + ))?; + + // Get the TDX module reference based on version + let (mrsigner, attributes) = if tdx_module_version > 0 { + ( + &tdx_module_identity.mrsigner, + &tdx_module_identity.attributes, + ) + } else { + let tdx_module = tcb_info.tdx_module.as_ref().unwrap(); + (&tdx_module.mrsigner, &tdx_module.attributes) + }; + + // Convert mrsigner and attributes to the appropriate type + let mrsigner_bytes: [u8; 48] = hex::decode(mrsigner).unwrap().try_into().unwrap(); + let attributes_bytes: [u8; 8] = hex::decode(attributes).unwrap().try_into().unwrap(); + + // Check for mismatches with a single validation + if mrsigner_bytes != td_report.mr_signer_seam { + return Err(anyhow::anyhow!( + "mrsigner mismatch between tdx module identity and tdx quote body" + )); + } + + if attributes_bytes != td_report.seam_attributes { + return Err(anyhow::anyhow!( + "attributes mismatch between tdx module identity and tdx quote body" + )); + } + + let tcb_level = tdx_module_identity + .tcb_levels + .iter() + .find(|level| level.in_tcb_level(tdx_module_isv_svn)) + .ok_or(anyhow::anyhow!( + "no tcb level found for tdx module identity within tdx module levels" + ))?; + + Ok(tcb_level.tcb_status) +} + +/// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/stable/Src/AttestationLibrary/src/Verifiers/Checks/TDRelaunchCheck.cpp +/// Returns a tuple of (tdx_relaunch_needed, tdx_relaunch_and_configuration_needed) +pub fn check_for_relaunch( + tcb_info: &TcbInfo, + td_report: &Td15ReportBody, + qe_tcb_status: QeTcbStatus, + sgx_tcb_status: TcbStatus, + tdx_tcb_status: TcbStatus, + tdx_module_tcb_status: TcbStatus, +) -> (bool, bool) { + let mut relaunch_needed = false; + let mut configuration_needed = false; + + if qe_tcb_status != QeTcbStatus::OutOfDate + || qe_tcb_status != QeTcbStatus::OutOfDateConfigurationNeeded + { + if !is_out_of_date(sgx_tcb_status) { + if is_out_of_date(tdx_tcb_status) { + if is_out_of_date(tdx_module_tcb_status) { + configuration_needed = is_configuration_needed(sgx_tcb_status) + || is_configuration_needed(tdx_tcb_status); + + let latest_tcb_level_tdx_svns = tcb_info + .tcb_levels + .first() + .unwrap() + .tcb + .tdx_tcb_components() + .unwrap(); + let tdx_module_version = td_report.tee_tcb_svn2[1]; + let tdx_module_svns = td_report.tee_tcb_svn2; + + if tdx_module_version == 0 { + if tdx_module_svns[0] >= latest_tcb_level_tdx_svns[0] + && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] + { + relaunch_needed = true; + } + } else { + let tdx_module_identity = + find_tdx_module_identity(tdx_module_version, tcb_info).unwrap(); + if tdx_module_svns[0] >= tdx_module_identity.tcb_levels[0].tcb.isvsvn + && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] + { + relaunch_needed = true; + } + } + } + } + } + } + + (relaunch_needed, configuration_needed) +} + +fn find_tdx_module_identity( + tdx_module_version: u8, + tcb_info: &TcbInfo, +) -> Option<&TdxModuleIdentity> { + let tdx_module_identity_id = format!("TDX_{:02x}", tdx_module_version); + + let tdx_module_identity = tcb_info + .tdx_module_identities + .as_ref() + .unwrap() + .iter() + .find(|identity| identity.id == tdx_module_identity_id); + + tdx_module_identity +} + +fn is_configuration_needed(tcb_status: TcbStatus) -> bool { + tcb_status == TcbStatus::ConfigurationAndSWHardeningNeeded + || tcb_status == TcbStatus::ConfigurationNeeded + || tcb_status == TcbStatus::OutOfDateConfigurationNeeded +} + +fn is_out_of_date(tcb_status: TcbStatus) -> bool { + tcb_status == TcbStatus::OutOfDate || tcb_status == TcbStatus::OutOfDateConfigurationNeeded +} diff --git a/src/types/collateral.rs b/src/types/collateral.rs index 7c3be35..a66cd18 100644 --- a/src/types/collateral.rs +++ b/src/types/collateral.rs @@ -1,6 +1,12 @@ +#[cfg(feature = "zero-copy")] +use crate::utils::cert_chain_processor; use crate::utils::{cert_chain, crl}; +use crate::utils::keccak; +use anyhow::Result; use serde::{Deserialize, Serialize}; -use x509_cert::{Certificate, crl::CertificateList}; +#[cfg(not(feature = "zero-copy"))] +use x509_cert::certificate::CertificateInner; +use x509_cert::{crl::CertificateList, der::{Decode, Encode}}; use super::{enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature}; @@ -26,7 +32,7 @@ pub struct Collateral { /// TCB Info and Identity Issuer Chain in PEM format /// Chain of certificates used to verify TCB Info and Identity signature. #[serde(with = "cert_chain")] - pub tcb_info_and_qe_identity_issuer_chain: Vec, + pub tcb_info_and_qe_identity_issuer_chain: Vec, /* Structured Data */ /// TCB Info Structure @@ -38,12 +44,70 @@ pub struct Collateral { pub qe_identity: QuotingEnclaveIdentityAndSignature, } +impl Collateral { + pub fn new( + root_ca_crl_der: &[u8], + pck_crl_der: &[u8], + tcb_info_and_qe_identity_issuer_chain_pem_bytes: &[u8], + tcb_info_json_str: &str, + qe_identity_json_str: &str, + ) -> Result { + let root_ca_crl = CertificateList::from_der(root_ca_crl_der)?; + let pck_crl = CertificateList::from_der(pck_crl_der)?; + #[cfg(not(feature = "zero-copy"))] + let tcb_info_and_qe_identity_issuer_chain: Vec = + CertificateInner::load_pem_chain(tcb_info_and_qe_identity_issuer_chain_pem_bytes)?; + #[cfg(feature = "zero-copy")] + let tcb_info_and_qe_identity_issuer_chain: Vec = + cert_chain_processor::load_pem_chain_bpf_friendly( + tcb_info_and_qe_identity_issuer_chain_pem_bytes, + )?; + let tcb_info: TcbInfoAndSignature = serde_json::from_str(tcb_info_json_str)?; + let qe_identity: QuotingEnclaveIdentityAndSignature = + serde_json::from_str(qe_identity_json_str)?; + + Ok(Self { + root_ca_crl, + pck_crl, + tcb_info_and_qe_identity_issuer_chain, + tcb_info, + qe_identity, + }) + } + + pub fn get_cert_hash(cert: &CertificateInner) -> Result<[u8; 32]> { + let tbs = cert.tbs_certificate.to_der()?; + Ok(keccak::hash(&tbs)) + } + + pub fn get_crl_hash(crl: &CertificateList) -> Result<[u8; 32]> { + let tbs = crl.tbs_cert_list.to_der()?; + Ok(keccak::hash(&tbs)) + } +} + #[cfg(test)] mod tests { use super::Collateral; #[test] - fn encode_decode_collateral_json() { + fn test_encode_collateral() { + let collateral = Collateral::new( + include_bytes!("../../data/intel_root_ca_crl.der"), + include_bytes!("../../data/pck_platform_crl.der"), + include_bytes!("../../data/tcb_signing_cert.pem"), + include_str!("../../data/tcb_info_v2.json"), + include_str!("../../data/qeidentityv2.json"), + ) + .expect("collateral to be created"); + + let json = serde_json::to_string(&collateral).expect("collateral to serialize"); + assert!(!json.is_empty(), "collateral JSON should not be empty"); + println!("Collateral JSON: {}", json); + } + + #[test] + fn test_decode_collateral_json() { let json = include_str!("../../data/full_collateral_sgx.json"); let _collateral: Collateral = serde_json::from_str(json).expect("json to parse"); } diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index ed8777e..e12ef74 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -1,9 +1,11 @@ -use anyhow::Context; +use anyhow::{Context, Result}; use chrono::Utc; use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; + use super::tcb_info::TcbStatus; +use crate::utils::keccak; const ENCLAVE_IDENTITY_V2: u32 = 2; @@ -54,7 +56,8 @@ impl QuotingEnclaveIdentityAndSignature { } pub fn get_enclave_identity(&self) -> anyhow::Result { - serde_json::from_str(self.enclave_identity_raw.get()).context("Failed to deserialize enclave identity") + serde_json::from_str(self.enclave_identity_raw.get()) + .context("Failed to deserialize enclave identity") } pub fn get_signature_bytes(&self) -> Vec { @@ -73,12 +76,10 @@ pub struct EnclaveIdentity { /// The time the Enclave Identity Information was created. The time shalle be in UTC /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - pub issue_date: chrono::DateTime, /// The time by which next Enclave Identity information will be issued. The time shall be in UTC /// and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - pub next_update: chrono::DateTime, /// A monotonically increasing sequence number changed when Intel updates the content of the TCB evaluation data set: @@ -86,7 +87,7 @@ pub struct EnclaveIdentity { /// flavours of SGX CPUs (Family-Model-Stepping-Platform-CustomSKU) and QE/QVE Identity. /// This sequence number allows users to easily determine when a particular TCB Info/QE Identity/QVE Identity /// superseedes another TCB Info/QE Identity/QVE Identity (value: current TCB Recovery event number stored in the database). - pub tcb_evaluation_data_number: u16, + pub tcb_evaluation_data_number: u32, /// Base 16-encoded string representing miscselect "golden" value (upon applying mask). pub miscselect: String, @@ -153,6 +154,21 @@ impl EnclaveIdentity { .map(|level| level.tcb_status.clone()) .unwrap_or(QeTcbStatus::Unspecified) } + + pub fn get_content_hash(&self) -> Result<[u8; 32]> { + let mut pre_image: Vec = vec![]; + pre_image.extend_from_slice(&[u8::from(self.id)]); + pre_image.extend_from_slice(&self.version.to_be_bytes()); + pre_image.extend_from_slice(&self.tcb_evaluation_data_number.to_be_bytes()); + pre_image.extend_from_slice(&self.miscselect_bytes()); + pre_image.extend_from_slice(&self.miscselect_mask_bytes()); + pre_image.extend_from_slice(&self.attributes_bytes()); + pre_image.extend_from_slice(&self.attributes_mask_bytes()); + pre_image.extend_from_slice(&self.mrsigner_bytes()); + pre_image.extend_from_slice(&self.isvprodid.to_be_bytes()); + pre_image.extend_from_slice(serde_json::to_vec(&self.tcb_levels)?.as_slice()); + Ok(keccak::hash(&pre_image)) + } } /// Enclave TCB level @@ -162,7 +178,6 @@ pub struct QeTcbLevel { /// SGX Enclave's ISV SVN pub tcb: QeTcb, /// The time the TCB was evaluated. The time shall be in UTC and the encoding shall be compliant to ISO 8601 standard (YYYY-MM-DDhh:mm:ssZ) - pub tcb_date: chrono::DateTime, /// TCB level status pub tcb_status: QeTcbStatus, @@ -172,7 +187,7 @@ pub struct QeTcbLevel { } /// TCB level status -#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, Copy, Eq, PartialEq)] #[repr(u8)] pub enum QeTcbStatus { UpToDate, @@ -192,7 +207,9 @@ impl std::fmt::Display for QeTcbStatus { QeTcbStatus::OutOfDate => write!(f, "OutOfDate"), QeTcbStatus::Revoked => write!(f, "Revoked"), QeTcbStatus::ConfigurationNeeded => write!(f, "ConfigurationNeeded"), - QeTcbStatus::ConfigurationAndSWHardeningNeeded => write!(f, "ConfigurationAndSWHardeningNeeded"), + QeTcbStatus::ConfigurationAndSWHardeningNeeded => { + write!(f, "ConfigurationAndSWHardeningNeeded") + }, QeTcbStatus::SWHardeningNeeded => write!(f, "SWHardeningNeeded"), QeTcbStatus::OutOfDateConfigurationNeeded => write!(f, "OutOfDateConfigurationNeeded"), QeTcbStatus::Unspecified => write!(f, "Unspecified"), @@ -307,10 +324,12 @@ mod tests { #[test] fn test_enclave_identity_serialization() { let qe_identity = include_bytes!("../../data/qeidentityv2_apiv4.json"); - let qe_identity: QuotingEnclaveIdentityAndSignature = serde_json::from_slice(qe_identity).unwrap(); + let qe_identity: QuotingEnclaveIdentityAndSignature = + serde_json::from_slice(qe_identity).unwrap(); let qe_identity_parsed = qe_identity.get_enclave_identity().unwrap(); - let original_qe_identity_hash = Sha256::digest(qe_identity.enclave_identity_raw.get().as_bytes()); + let original_qe_identity_hash = + Sha256::digest(qe_identity.enclave_identity_raw.get().as_bytes()); let serialized_qe_identity = serde_json::to_string(&qe_identity_parsed).unwrap(); let serialized_qe_identity_hash = Sha256::digest(serialized_qe_identity.as_bytes()); diff --git a/src/types/mod.rs b/src/types/mod.rs index 5c1fdb2..9931971 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,30 +1,103 @@ +use alloy_sol_types::SolValue; + +use super::types::report::{EnclaveReportBody, Td10ReportBody, Td15ReportBody}; +use anyhow::Result; use quote::QuoteBody; #[cfg(feature = "full")] pub mod collateral; #[cfg(feature = "full")] -pub mod tcb_info; -#[cfg(feature = "full")] pub mod enclave_identity; +pub mod pod; pub mod quote; pub mod report; pub mod sgx_x509; -pub mod pod; +#[cfg(feature = "full")] +pub mod tcb_info; -pub type UInt16LE = zerocopy::little_endian::U16; -pub type UInt32LE = zerocopy::little_endian::U32; -pub type UInt64LE = zerocopy::little_endian::U64; +const ENCLAVE_REPORT_LEN: usize = 384; // SGX_ENCLAVE_REPORT +const TD10_REPORT_LEN: usize = 584; // TD10_REPORT +const TD15_REPORT_LEN: usize = 648; // TD15_REPORT // serialization: -// [quote_vesion][tee_type][tcb_status][fmspc][quote_body_raw_bytes][abi-encoded string array of tcb_advisory_ids] -// 2 bytes + 4 bytes + 1 byte + 6 bytes + var (SGX_ENCLAVE_REPORT = 384; TD10_REPORT = 584) + var +// [quote_vesion][quote_body_type][tcb_status][fmspc][quote_body_raw_bytes][abi-encoded string array of tcb_advisory_ids] +// 2 bytes + 2 bytes + 1 byte + 6 bytes + var (SGX_ENCLAVE_REPORT = 384; TD10_REPORT = 584; TD15_REPORT = 648) + var // total: 13 + (384 or 584) + var bytes #[derive(Debug)] pub struct VerifiedOutput { pub quote_version: u16, - pub tee_type: u32, + pub quote_body_type: u16, pub tcb_status: u8, pub fmspc: [u8; 6], pub quote_body: QuoteBody, pub advisory_ids: Option>, } + +impl VerifiedOutput { + pub fn to_vec(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.quote_version.to_be_bytes()); + bytes.extend_from_slice(&self.quote_body_type.to_be_bytes()); + bytes.push(self.tcb_status); + bytes.extend_from_slice(&self.fmspc); + bytes.extend_from_slice(self.quote_body.as_bytes()); + + if let Some(ref ids) = self.advisory_ids { + let encoded = ids.abi_encode(); + bytes.extend_from_slice(&encoded); + } + + bytes + } + + pub fn from_bytes(slice: &[u8]) -> Result { + let mut quote_version = [0; 2]; + quote_version.copy_from_slice(&slice[0..2]); + let mut quote_body_type = [0; 2]; + quote_body_type.copy_from_slice(&slice[2..4]); + let tcb_status = slice[4]; + let mut fmspc = [0; 6]; + fmspc.copy_from_slice(&slice[5..11]); + + let mut offset = 11usize; + let quote_body_type = u16::from_be_bytes(quote_body_type); + let quote_body = match quote_body_type{ + 1 => { + let raw_quote_body: [u8; ENCLAVE_REPORT_LEN] = slice + [offset..offset + ENCLAVE_REPORT_LEN] + .try_into() + .unwrap(); + offset += ENCLAVE_REPORT_LEN; + QuoteBody::SgxQuoteBody(EnclaveReportBody::try_from(raw_quote_body)?) + }, + 2 => { + let raw_quote_body: [u8; TD10_REPORT_LEN] = + slice[offset..offset + TD10_REPORT_LEN].try_into()?; + offset += TD10_REPORT_LEN; + QuoteBody::Td10QuoteBody(Td10ReportBody::try_from(raw_quote_body)?) + }, + 3 => { + let raw_quote_body: [u8; TD15_REPORT_LEN] = + slice[offset..offset + TD15_REPORT_LEN].try_into()?; + offset += TD15_REPORT_LEN; + QuoteBody::Td15QuoteBody(Td15ReportBody::try_from(raw_quote_body)?) + }, + _ => panic!("unknown QuoteBody type"), + }; + + let mut advisory_ids = None; + if offset < slice.len() { + let advisory_ids_slice = &slice[offset..]; + advisory_ids = Some(>::abi_decode(advisory_ids_slice)?); + } + + Ok(VerifiedOutput { + quote_version: u16::from_be_bytes(quote_version), + quote_body_type, + tcb_status, + fmspc, + quote_body, + advisory_ids, + }) + } +} diff --git a/src/types/pod/enclave_identity/mod.rs b/src/types/pod/enclave_identity/mod.rs index ed12deb..5a1772f 100644 --- a/src/types/pod/enclave_identity/mod.rs +++ b/src/types/pod/enclave_identity/mod.rs @@ -38,14 +38,15 @@ pub struct EnclaveIdentityHeader { pub version: u32, pub issue_date_timestamp: i64, pub next_update_timestamp: i64, - pub tcb_evaluation_data_number: u16, + pub tcb_evaluation_data_number: u32, pub isvprodid: u16, + pub _final_padding: [u8; 2], // Hex strings (fixed-size byte arrays storing UTF-8 hex characters) pub miscselect_hex: [u8; 8], pub miscselect_mask_hex: [u8; 8], pub attributes_hex: [u8; 32], pub attributes_mask_hex: [u8; 32], - pub mrsigner_hex: [u8; 64], // 64 hex chars for a 32-byte value + pub mrsigner_hex: [u8; 64], pub tcb_levels_count: u32, pub tcb_levels_total_payload_len: u32, // Total byte length for all QeTcbLevelPodHeader sections + their payloads @@ -55,7 +56,8 @@ pub struct EnclaveIdentityHeader { // version (4) = 4 // issue_date (8) = 8 // next_update (8) = 8 - // tcb_eval_num (2) + isvprodid (2) = 4 + // tcb_eval_num (4) = 4 + // isvprodid (2) + _final_padding (2) = 4 // miscselect (8) = 8 // miscselect_mask (8) = 8 // attributes (32) = 32 @@ -63,8 +65,8 @@ pub struct EnclaveIdentityHeader { // mrsigner (64) = 64 // tcb_levels_count (4) = 4 // tcb_levels_total_payload_len (4) = 4 - // Total = 4+4+8+8+4+8+8+32+32+64+4+4 = 180. Needs to be 184 for multiple of 8. - pub _final_padding: [u8; 4], + // Total = 4+4+8+8+4+4+8+8+32+32+64+4+4 = 184 + } /// The top-level POD structure for Enclave Identity and its signature. diff --git a/src/types/pod/enclave_identity/zero_copy/structs.rs b/src/types/pod/enclave_identity/zero_copy/structs.rs index 30f8549..92ad469 100644 --- a/src/types/pod/enclave_identity/zero_copy/structs.rs +++ b/src/types/pod/enclave_identity/zero_copy/structs.rs @@ -50,7 +50,7 @@ impl<'a> EnclaveIdentityZeroCopy<'a> { pub fn version(&self) -> u32 { self.header.version } pub fn issue_date_timestamp(&self) -> i64 { self.header.issue_date_timestamp } pub fn next_update_timestamp(&self) -> i64 { self.header.next_update_timestamp } - pub fn tcb_evaluation_data_number(&self) -> u16 { self.header.tcb_evaluation_data_number } + pub fn tcb_evaluation_data_number(&self) -> u32 { self.header.tcb_evaluation_data_number } pub fn isvprodid(&self) -> u16 { self.header.isvprodid } pub fn miscselect_bytes(&self) -> [u8; 4] { hex::decode(&self.header.miscselect_hex) diff --git a/src/types/quote/body.rs b/src/types/quote/body.rs index 02348b1..4b2d15a 100644 --- a/src/types/quote/body.rs +++ b/src/types/quote/body.rs @@ -1,6 +1,6 @@ use zerocopy::AsBytes; -use crate::types::report::{EnclaveReportBody, Td10ReportBody}; +use crate::types::report::{EnclaveReportBody, Td10ReportBody, Td15ReportBody}; use super::{SGX_TEE_TYPE, TDX_TEE_TYPE}; @@ -9,6 +9,7 @@ use super::{SGX_TEE_TYPE, TDX_TEE_TYPE}; pub enum QuoteBody { SgxQuoteBody(EnclaveReportBody), Td10QuoteBody(Td10ReportBody), + Td15QuoteBody(Td15ReportBody), } impl QuoteBody { @@ -16,6 +17,7 @@ impl QuoteBody { match self { Self::SgxQuoteBody(body) => body.as_bytes(), Self::Td10QuoteBody(body) => body.as_bytes(), + Self::Td15QuoteBody(body) => body.as_bytes(), } } @@ -23,12 +25,14 @@ impl QuoteBody { match self { Self::SgxQuoteBody(_) => SGX_TEE_TYPE, Self::Td10QuoteBody(_) => TDX_TEE_TYPE, + Self::Td15QuoteBody(_) => TDX_TEE_TYPE, } } pub fn as_tdx_report_body(&self) -> Option<&Td10ReportBody> { match self { Self::Td10QuoteBody(body) => Some(body), + Self::Td15QuoteBody(body) => Some(&body.td_report), _ => None, } } diff --git a/src/types/quote/header.rs b/src/types/quote/header.rs index 2b2dd6f..03a271f 100644 --- a/src/types/quote/header.rs +++ b/src/types/quote/header.rs @@ -1,4 +1,3 @@ -use super::{QUOTE_V3, QUOTE_V4}; use anyhow::anyhow; use zerocopy::little_endian; @@ -6,7 +5,7 @@ pub const INTEL_QE_VENDOR_ID: [u8; 16] = [ 0x93, 0x9A, 0x72, 0x33, 0xF7, 0x9C, 0x4C, 0xA9, 0x94, 0x0A, 0x0D, 0xB3, 0x95, 0x7F, 0x06, 0x07, ]; -/// Header of the SGX Quote data structure. +/// Header of the DCAP Quote data structure. /// /// We use zerocopy for zero-copy parsing of the quote header from raw bytes. /// This allows us to safely interpret the raw byte slice as a structured type without copying the data. @@ -64,8 +63,8 @@ impl TryFrom<[u8; std::mem::size_of::()]> for QuoteHeader { let quote_header = ::read_from(&value).expect("failed to read quote header"); - if quote_header.version.get() != QUOTE_V3 && quote_header.version.get() != QUOTE_V4 { - return Err(anyhow!("unsupported quote version")); + if quote_header.version.get() < 3 || quote_header.version.get() > 5 { + return Err(anyhow!("unsupported quote version: {}", quote_header.version)); } if quote_header.attestation_key_type.get() != AttestationKeyType::Ecdsa256P256 as u16 { diff --git a/src/types/quote/mod.rs b/src/types/quote/mod.rs index fb521c7..d46ce2d 100644 --- a/src/types/quote/mod.rs +++ b/src/types/quote/mod.rs @@ -11,12 +11,7 @@ pub use signature::*; use crate::utils; -use super::report::{EnclaveReportBody, Td10ReportBody}; - -#[allow(non_snake_case)] -const QUOTE_V3: u16 = 3; -#[allow(non_snake_case)] -const QUOTE_V4: u16 = 4; +use super::report::*; pub const SGX_TEE_TYPE: u32 = 0x00000000; pub const TDX_TEE_TYPE: u32 = 0x00000081; @@ -24,13 +19,10 @@ pub const TDX_TEE_TYPE: u32 = 0x00000081; /// A DCAP quote, used for verification. #[derive(Debug)] pub struct Quote<'a> { - /// Header of the SGX Quote data structure. pub header: QuoteHeader, - - /// Software Vendor enclave report. + pub body_type: u16, + pub body_size: u32, pub body: QuoteBody, - - /// Signature of the quote body. pub signature: QuoteSignatureData<'a>, } @@ -45,27 +37,75 @@ impl<'a> Quote<'a> { .ok_or_else(|| anyhow!("underflow reading quote header"))?; // Read the quote body and signature - if quote_header.tee_type == SGX_TEE_TYPE { - let quote_body = utils::read_from_bytes::(bytes) - .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; - let quote_signature = QuoteSignatureData::read(bytes, quote_header.version.get())?; - Ok(Quote { - header: quote_header, - body: QuoteBody::SgxQuoteBody(quote_body), - signature: quote_signature, - }) - } else if quote_header.tee_type == TDX_TEE_TYPE { - let quote_body = utils::read_from_bytes::(bytes) - .ok_or_else(|| anyhow!("underflow reading td10 report body"))?; - let quote_signature = QuoteSignatureData::read(bytes, quote_header.version.get())?; - - return Ok(Quote { - header: quote_header, - body: QuoteBody::Td10QuoteBody(quote_body), - signature: quote_signature, - }); + let quote_body_type; + let quote_body_size; + let quote_body = if quote_header.version.get() <= 4 { + if quote_header.tee_type == SGX_TEE_TYPE { + quote_body_type = 1; + quote_body_size = std::mem::size_of::() as u32; + let isv_report_body = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; + QuoteBody::SgxQuoteBody(isv_report_body) + } else if quote_header.tee_type == TDX_TEE_TYPE { + quote_body_type = 2; + quote_body_size = std::mem::size_of::() as u32; + let td_report = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading td10 report body"))?; + QuoteBody::Td10QuoteBody(td_report) + } else { + return Err(anyhow!("unsupported TEE type")); + } } else { - return Err(anyhow!("unsupported quote version")); - } + quote_body_type = u16::from_le_bytes([bytes[0], bytes[1]]); + *bytes = &bytes[2..]; + + quote_body_size = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + *bytes = &bytes[4..]; + + if quote_body_type == 1 { + if quote_header.tee_type != SGX_TEE_TYPE { + return Err(anyhow!("Quote body type 1 must be SGX TEE type")); + } + if quote_body_size as usize != std::mem::size_of::() { + return Err(anyhow!("Quote body size mismatch for SGX TEE type")); + } + let isv_report_body = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading enclave report body"))?; + QuoteBody::SgxQuoteBody(isv_report_body) + } else if quote_body_type == 2 { + if quote_header.tee_type != TDX_TEE_TYPE { + return Err(anyhow!("Quote body type 2 must be TDX TEE type")); + } + if quote_body_size as usize != std::mem::size_of::() { + return Err(anyhow!("Quote body size mismatch for TDX TEE type")); + } + let td_report = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading td10 report body"))?; + QuoteBody::Td10QuoteBody(td_report) + } else if quote_body_type == 3 { + if quote_header.tee_type != TDX_TEE_TYPE { + return Err(anyhow!("Quote body type 3 must be TDX TEE type")); + } + if quote_body_size as usize != std::mem::size_of::() { + return Err(anyhow!("Quote body size mismatch for TDX TEE type")); + } + let td_report = utils::read_from_bytes::(bytes) + .ok_or_else(|| anyhow!("underflow reading td15 report body"))?; + QuoteBody::Td15QuoteBody(td_report) + } else { + return Err(anyhow!("unsupported quote body type")); + } + }; + + // Read the quote signature + let quote_signature = QuoteSignatureData::read(bytes, quote_header.version.get())?; + + Ok(Quote { + header: quote_header, + body_type: quote_body_type, + body_size: quote_body_size, + body: quote_body, + signature: quote_signature, + }) } } diff --git a/src/types/quote/signature.rs b/src/types/quote/signature.rs index c48b154..3795351 100644 --- a/src/types/quote/signature.rs +++ b/src/types/quote/signature.rs @@ -46,10 +46,12 @@ impl<'a> QuoteSignatureData<'a> { return Err(anyhow!("underflow reading signature")); } - match version { - 3 => Self::read_v3_signature(bytes), - 4 => Self::read_v4_signature(bytes), - _ => Err(anyhow!("unsupported quote version")), + if version == 3 { + Self::read_v3_signature(bytes) + } else if version >= 4 { + Self::read_v4_signature(bytes) + } else { + Err(anyhow!("unsupported quote version")) } } diff --git a/src/types/report.rs b/src/types/report.rs index 7932db8..a743af8 100644 --- a/src/types/report.rs +++ b/src/types/report.rs @@ -4,8 +4,6 @@ use zerocopy::little_endian; const SGX_CPUSVN_SIZE: usize = 16; const SGX_HASH_SIZE: usize = 32; -/// EnclaveReportBody is the body of the SGX report. -/// #[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] #[repr(C)] pub struct EnclaveReportBody { @@ -88,7 +86,6 @@ impl TryFrom<[u8; std::mem::size_of::()]> for EnclaveReportBo } } -/// Td10ReportBody is the body of the TDX 1.0 Quotes #[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] #[repr(C)] pub struct Td10ReportBody { @@ -167,3 +164,28 @@ impl TryFrom<[u8; std::mem::size_of::()]> for Td10ReportBody { Ok(report) } } + +#[derive(Debug, zerocopy::FromBytes, zerocopy::FromZeroes, zerocopy::AsBytes)] +#[repr(C)] +pub struct Td15ReportBody { + pub td_report: Td10ReportBody, + + /// (584) Describes the current TCB of TDX. This value may will be different than TEE_TCB_SVN by + /// loading a new version of the TDX Module using the TD Preserving update capability) + pub tee_tcb_svn2: [u8; 16], + + /// (600) Measurement of the initial contents of the Migration TD + pub mr_service_td: [u8; 48] + // Total 648 bytes +} + +impl TryFrom<[u8; std::mem::size_of::()]> for Td15ReportBody { + type Error = anyhow::Error; + + fn try_from(value: [u8; std::mem::size_of::()]) -> Result { + let report = ::read_from(&value) + .expect("failed to read tdx report body"); + + Ok(report) + } +} \ No newline at end of file diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index ea0e228..2e1ac8e 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -1,13 +1,19 @@ use std::{str::from_utf8, time::SystemTime}; -use anyhow::{Context, bail}; +use anyhow::{Result, Context, bail}; use chrono::{DateTime, Utc}; use p256::ecdsa::VerifyingKey; use p256::ecdsa::signature::Verifier; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; -use super::{quote::{Quote, QuoteBody}, report::Td10ReportBody, sgx_x509::SgxPckExtension}; +use crate::types::{quote::TDX_TEE_TYPE, report::Td10ReportBody}; +use crate::utils::keccak; + +use super::{ + quote::{Quote, QuoteBody}, + sgx_x509::SgxPckExtension, +}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TcbInfoAndSignature { @@ -105,9 +111,9 @@ pub struct TcbInfo { #[serde(skip_serializing_if = "Option::is_none", rename = "id")] pub id: Option, pub version: TcbInfoVersion, - + pub issue_date: chrono::DateTime, - + pub next_update: chrono::DateTime, pub fmspc: String, pub pce_id: String, @@ -135,68 +141,6 @@ impl TcbInfo { .unwrap() } - pub fn verify_tdx_module(&self, quote_body: &Td10ReportBody) -> anyhow::Result { - if self.tdx_module.is_none() { - return Err(anyhow::anyhow!("no tdx module found in tcb info")); - } - - let (tdx_module_isv_svn, tdx_module_version) = - (quote_body.tee_tcb_svn[0], quote_body.tee_tcb_svn[1]); - let tdx_module_identity_id = format!("TDX_{:02x}", tdx_module_version); - - if self.tdx_module_identities.is_none() { - return Err(anyhow::anyhow!( - "no tdx module identities found in tcb info" - )); - } - - let tdx_module_identity = self - .tdx_module_identities - .as_ref() - .unwrap() - .iter() - .find(|identity| identity.id == tdx_module_identity_id) - .ok_or(anyhow::anyhow!("tdx module identity not found in tcb info"))?; - - // Get the TDX module reference based on version - let (mrsigner, attributes) = if tdx_module_version > 0 { - ( - &tdx_module_identity.mrsigner, - &tdx_module_identity.attributes, - ) - } else { - let tdx_module = self.tdx_module.as_ref().unwrap(); - (&tdx_module.mrsigner, &tdx_module.attributes) - }; - - // Convert mrsigner and attributes to the appropriate type - let mrsigner_bytes: [u8; 48] = hex::decode(mrsigner).unwrap().try_into().unwrap(); - let attributes_bytes: [u8; 8] = hex::decode(attributes).unwrap().try_into().unwrap(); - - // Check for mismatches with a single validation - if mrsigner_bytes != quote_body.mr_signer_seam { - return Err(anyhow::anyhow!( - "mrsigner mismatch between tdx module identity and tdx quote body" - )); - } - - if attributes_bytes != quote_body.seam_attributes { - return Err(anyhow::anyhow!( - "attributes mismatch between tdx module identity and tdx quote body" - )); - } - - let tcb_level = tdx_module_identity - .tcb_levels - .iter() - .find(|level| level.in_tcb_level(tdx_module_isv_svn)) - .ok_or(anyhow::anyhow!( - "no tcb level found for tdx module identity within tdx module levels" - ))?; - - Ok(tcb_level.tcb_status) - } - pub fn converge_tcb_status_with_tdx_module( platform_status: TcbStatus, tdx_module_status: TcbStatus, @@ -242,13 +186,47 @@ impl TcbInfo { _ => platform_status, } } + + pub fn get_content_hash(&self) -> Result<[u8; 32]> { + let id: u8 = match &self.id { + Some(id) => { + if id == "SGX" { + 0 + } else if id == "TDX" { + 1 + } else { + panic!("Unsupported TCB Info ID: {}", id); + } + }, + None => 0, + }; + + let mut pre_image: Vec = vec![]; + pre_image.extend_from_slice(&[self.tcb_type]); + pre_image.extend_from_slice(&[id]); + pre_image.extend_from_slice(&u32::from(self.version).to_be_bytes()); + pre_image.extend_from_slice(&self.tcb_evaluation_data_number.to_be_bytes()); + pre_image.extend_from_slice(&self.fmspc_bytes()); + pre_image.extend_from_slice(&self.pce_id_bytes()); + pre_image.extend_from_slice(serde_json::to_vec(&self.tcb_levels)?.as_slice()); + + if let Some(tdx_module) = &self.tdx_module { + pre_image.extend_from_slice(&serde_json::to_vec(tdx_module)?); + } + + if let Some(tdx_module_identities) = &self.tdx_module_identities { + pre_image.extend_from_slice(&serde_json::to_vec(tdx_module_identities)?); + } + + Ok(keccak::hash(&pre_image)) + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TcbLevel { pub tcb: Tcb, - + pub tcb_date: chrono::DateTime, pub tcb_status: TcbStatus, #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] @@ -267,21 +245,8 @@ pub enum TcbStatus { OutOfDateConfigurationNeeded, Revoked, Unspecified, -} - -impl std::fmt::Display for TcbStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TcbStatus::UpToDate => write!(f, "UpToDate"), - TcbStatus::OutOfDate => write!(f, "OutOfDate"), - TcbStatus::ConfigurationNeeded => write!(f, "ConfigurationNeeded"), - TcbStatus::SWHardeningNeeded => write!(f, "SWHardeningNeeded"), - TcbStatus::ConfigurationAndSWHardeningNeeded => write!(f, "ConfigurationAndSWHardeningNeeded"), - TcbStatus::OutOfDateConfigurationNeeded => write!(f, "OutOfDateConfigurationNeeded"), - TcbStatus::Revoked => write!(f, "Revoked"), - TcbStatus::Unspecified => write!(f, "Unspecified"), - } - } + RelaunchAdvised, + RelaunchAdvisedConfigurationNeeded, } impl TryFrom for TcbStatus { @@ -296,6 +261,8 @@ impl TryFrom for TcbStatus { 5 => Ok(TcbStatus::OutOfDateConfigurationNeeded), 6 => Ok(TcbStatus::Revoked), 7 => Ok(TcbStatus::Unspecified), + 8 => Ok(TcbStatus::RelaunchAdvised), + 9 => Ok(TcbStatus::RelaunchAdvisedConfigurationNeeded), _ => Err("Unsupported TCB status"), } } @@ -338,10 +305,9 @@ pub struct TcbComponentV3 { #[serde(skip_serializing_if = "Option::is_none")] pub category: Option, #[serde(skip_serializing_if = "Option::is_none", rename = "type")] - pub component_type: Option + pub component_type: Option, } - #[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug)] pub struct TcbV2 { pub sgxtcbcomp01svn: u8, @@ -404,15 +370,13 @@ impl Tcb { pub fn tdx_tcb_components(&self) -> Option<[u8; 16]> { match self { Self::V2(_) => None, - Self::V3(v3) => { - v3.tdxtcbcomponents.as_ref().map(|components| { - let mut result = [0u8; 16]; - for i in 0..16 { - result[i] = components[i].svn; - } - result - }) - }, + Self::V3(v3) => v3.tdxtcbcomponents.as_ref().map(|components| { + let mut result = [0u8; 16]; + for i in 0..16 { + result[i] = components[i].svn; + } + result + }), } } } @@ -488,7 +452,7 @@ impl TdxModuleIdentity { #[serde(rename_all = "camelCase")] pub struct TdxTcbLevel { pub tcb: TcbTdx, - + pub tcb_date: chrono::DateTime, pub tcb_status: TcbStatus, #[serde(rename = "advisoryIDs", skip_serializing_if = "Option::is_none")] @@ -507,7 +471,6 @@ pub struct TcbTdx { } impl TcbStatus { - /// Determine the status of the TCB level that is trustable for the platform /// /// This function performs TCB (Trusted Computing Base) level verification by: @@ -530,54 +493,79 @@ impl TcbStatus { .tcb_levels .iter() .enumerate() - .find(|(_, level)| TcbStatus::pck_in_tcb_level(level, pck_extension)) + .find(|(_, level)| pck_in_tcb_level(level, pck_extension)) .ok_or_else(|| anyhow::anyhow!("unsupported TCB in pck extension"))?; // Extract the SGX TCB status and advisories from the matching level let sgx_tcb_status = first_matching_level.tcb_status; - let mut advisory_ids = first_matching_level.advisory_ids.clone().unwrap_or_default(); + let mut advisory_ids = first_matching_level + .advisory_ids + .clone() + .unwrap_or_default(); // Default TDX TCB status to Unspecified // Will be updated if a valid TDX module is found in the quote let mut tdx_tcb_status = TcbStatus::Unspecified; - // Check if the quote contains a TDX module (TD 1.0 Quote Body) - if let QuoteBody::Td10QuoteBody(body) = "e.body { - // Start iterating from the found sgx matching level - for level in &tcb_info.tcb_levels[index..] { - // Process each level starting from the matching one - if let Some(tdx_tcb_components) = level.tcb.tdx_tcb_components() { - let components_match = tdx_tcb_components - .iter() - .zip(body.tee_tcb_svn.iter()) - .all(|(&comp, &svn)| comp <= svn); - - if components_match { - tdx_tcb_status = level.tcb_status; - advisory_ids = level.advisory_ids.clone().unwrap_or_default(); - break; - } - } else { - // This should not happen, meaning if you have a Td10QuoteBody, you should have a TDX TCB Component present in the TCB Info - return Err(anyhow::anyhow!("did not find tdx tcb components in tcb info when Td10QuoteBody is provided for the quote")); - } - } + if quote.header.tee_type == TDX_TEE_TYPE { + let td_report = match "e.body { + QuoteBody::Td10QuoteBody(report) => report, + QuoteBody::Td15QuoteBody(report) => &report.td_report, + _ => bail!("TDX Quote should only contain Td10 or Td15 report"), + }; + + let matched_tcb_level = match_tdx_tcb(td_report, tcb_info, index)?; + tdx_tcb_status = matched_tcb_level.tcb_status; + advisory_ids = matched_tcb_level.advisory_ids.clone().unwrap_or_default(); } // Return the final status determination as a tuple Ok((sgx_tcb_status, tdx_tcb_status, advisory_ids)) } +} - /// Returns true if all the pck componenets are >= all the tcb level components and e - /// the pck pcesvn is >= the tcb level pcesvn. - fn pck_in_tcb_level(level: &TcbLevel, pck_extension: &SgxPckExtension) -> bool { - const SVN_LENGTH: usize = 16; - let pck_components: &[u8; SVN_LENGTH] = &pck_extension.tcb.compsvn; +/// Returns true if all the pck componenets are >= all the tcb level components and +/// the pck pcesvn is >= the tcb level pcesvn. +fn pck_in_tcb_level(level: &TcbLevel, pck_extension: &SgxPckExtension) -> bool { + const SVN_LENGTH: usize = 16; + let pck_components: &[u8; SVN_LENGTH] = &pck_extension.tcb.compsvn; + + pck_components + .iter() + .zip(level.tcb.sgx_tcb_components()) + .all(|(&pck, tcb)| pck >= tcb) + && pck_extension.tcb.pcesvn >= level.tcb.pcesvn() +} - pck_components - .iter() - .zip(level.tcb.sgx_tcb_components()) - .all(|(&pck, tcb)| pck >= tcb) - && pck_extension.tcb.pcesvn >= level.tcb.pcesvn() +fn match_tdx_tcb( + td_report: &Td10ReportBody, + tcb_info: &TcbInfo, + index: usize, +) -> anyhow::Result { + let matching_level: TcbLevel; + + // Start iterating from the found sgx matching level + for level in &tcb_info.tcb_levels[index..] { + // Process each level starting from the matching one + if let Some(tdx_tcb_components) = level.tcb.tdx_tcb_components() { + let components_match = tdx_tcb_components + .iter() + .zip(td_report.tee_tcb_svn.iter()) + .all(|(&comp, &svn)| comp <= svn); + + if components_match { + // tdx_tcb_status = level.tcb_status; + // advisory_ids = level.advisory_ids.clone().unwrap_or_default(); + matching_level = level.clone(); + return Ok(matching_level); + } + } else { + // This should not happen, meaning if you have a Td10QuoteBody, you should have a TDX TCB Component present in the TCB Info + break; + } } + + Err(anyhow::anyhow!( + "can not find tdx tcb components in tcb info for TDX Quote Body" + )) } diff --git a/src/utils.rs b/src/utils.rs index eed4813..12eac1d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,18 @@ use std::time::SystemTime; - use x509_cert::{certificate::CertificateInner, crl::CertificateList}; +pub mod keccak { + use tiny_keccak::{Hasher, Keccak}; + + pub fn hash(data: &[u8]) -> [u8; 32] { + let mut hasher = Keccak::v256(); + let mut output = [0u8; 32]; + hasher.update(data); + hasher.finalize(&mut output); + output + } +} + /// A module for serializing and deserializing certificate chains. pub mod cert_chain { use serde::{Deserialize, de, ser}; @@ -64,7 +75,7 @@ pub mod u32_hex { use serde::Serializer; use zerocopy::AsBytes; - use crate::types::UInt32LE; + type UInt32LE = zerocopy::little_endian::U32; pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result where diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..9f02710 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,81 @@ +use std::time::{Duration, SystemTime}; + +use x509_cert::{crl::CertificateList, der::Decode}; + +use dcap_rs::types::tcb_info::TcbInfoAndSignature; +use dcap_rs::{ + types::enclave_identity::QuotingEnclaveIdentityAndSignature, + utils::cert_chain_processor, + types::{collateral::Collateral, quote::Quote}, +}; + +pub fn sgx_quote_data() -> (Collateral, Quote<'static>) { + let collateral = include_str!("../../data/full_collateral_sgx.json"); + let collateral: Collateral = serde_json::from_str(collateral).unwrap(); + let quote = include_bytes!("../../data/quote_sgx.bin"); + let quote = Quote::read(&mut quote.as_slice()).unwrap(); + (collateral, quote) +} + +pub fn tdx_quote_data() -> (Collateral, Quote<'static>) { + let quote = include_bytes!("../../data/quote_tdx.bin"); + let quote = Quote::read(&mut quote.as_slice()).unwrap(); + + let tcb_info_and_qe_identity_issuer_chain = include_bytes!("../../data/signing_cert.pem"); + let tcb_info_and_qe_identity_issuer_chain = + cert_chain_processor::load_pem_chain_bpf_friendly(tcb_info_and_qe_identity_issuer_chain) + .unwrap(); + + let root_ca_crl = include_bytes!("../../data/intel_root_ca_crl.der"); + let root_ca_crl = CertificateList::from_der(root_ca_crl).unwrap(); + + let tcb_info = include_bytes!("../../data/tcb_info_v3_with_tdx_module.json"); + let tcb_info: TcbInfoAndSignature = serde_json::from_slice(tcb_info).unwrap(); + + let qe_identity = include_bytes!("../../data/qeidentityv2_apiv4.json"); + let qe_identity: QuotingEnclaveIdentityAndSignature = + serde_json::from_slice(qe_identity).unwrap(); + + let platform_ca_crl = include_bytes!("../../data/pck_platform_crl.der"); + let platform_ca_crl = CertificateList::from_der(platform_ca_crl).unwrap(); + + let collateral = Collateral { + tcb_info_and_qe_identity_issuer_chain, + root_ca_crl, + pck_crl: platform_ca_crl, + tcb_info, + qe_identity, + }; + (collateral, quote) +} + +pub fn tdx_quote_v5_data() -> (Collateral, Quote<'static>) { + let quote = include_bytes!("../../data/v5/alibaba_quote_5.dat"); + let quote = Quote::read(&mut quote.as_slice()).unwrap(); + + let collateral = Collateral::new( + include_bytes!("../../data/intel_root_ca_crl.der"), + include_bytes!("../../data/pck_platform_crl.der"), + include_bytes!("../../data/v5/signing_cert.pem"), + include_str!("../../data/v5/tcbinfov3_90C06F000000.json"), + include_str!("../../data/v5/qe_td.json"), + ) + .expect("Failed to load collaterals"); + + (collateral, quote) +} + +pub fn test_sgx_time() -> SystemTime { + // Aug 29th 4:20pm, ~24 hours after quote was generated + SystemTime::UNIX_EPOCH + Duration::from_secs(1724962800) +} + +pub fn test_tdx_time() -> SystemTime { + // Pinned September 10th, 2024, 6:49am GMT + SystemTime::UNIX_EPOCH + Duration::from_secs(1725950994) +} + +pub fn test_tdx_v5_time() -> SystemTime { + // June 15th, 2025, 12am UTC + SystemTime::UNIX_EPOCH + Duration::from_secs(1749945600) +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..0e4c560 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,47 @@ +mod common; + +use dcap_rs::{verify_dcap_quote, types::quote::Quote}; +use common::*; + +#[test] +fn parse_tdx_v5_quote() { + let bytes = include_bytes!("../data/v5/alibaba_quote_5.dat"); + let quote = Quote::read(&mut bytes.as_slice()).unwrap(); + println!("{:?}", quote); +} + +#[test] +fn parse_tdx_v4_quote() { + let bytes = include_bytes!("../data/quote_tdx.bin"); + let quote = Quote::read(&mut bytes.as_slice()).unwrap(); + println!("{:?}", quote); +} + +#[test] +fn parse_sgx_quote() { + let bytes = include_bytes!("../data/quote_sgx.bin"); + let quote = Quote::read(&mut bytes.as_slice()).unwrap(); + println!("{:?}", quote); +} + +#[test] +fn e2e_sgx_quote() { + let (collateral, quote) = sgx_quote_data(); + verify_dcap_quote(test_sgx_time(), collateral, quote) + .expect("certificate chain integrity should succeed"); +} + +#[test] +fn e2e_tdx_quote() { + let (collateral, quote) = tdx_quote_data(); + verify_dcap_quote(test_tdx_time(), collateral, quote) + .expect("certificate chain integrity should succeed"); +} + +#[test] +fn e2e_tdx_v5_quote() { + let (collateral, quote) = tdx_quote_v5_data(); + let output = verify_dcap_quote(test_tdx_v5_time(), collateral, quote) + .expect("certificate chain integrity should succeed"); + println!("{:?}", output); +} From 8b199ed574fbab717e62d4031578632d949c99bb Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Fri, 4 Jul 2025 15:24:19 +0800 Subject: [PATCH 09/14] version upgrade, code linted and formatted --- Cargo.toml | 2 +- src/tdx.rs | 65 +++--- src/types/collateral.rs | 7 +- src/types/enclave_identity.rs | 2 +- src/types/mod.rs | 2 +- src/types/pod/enclave_identity/mod.rs | 45 ++-- src/types/pod/enclave_identity/serialize.rs | 28 +-- .../enclave_identity/zero_copy/conversion.rs | 6 +- .../pod/enclave_identity/zero_copy/error.rs | 13 +- .../enclave_identity/zero_copy/iterators.rs | 28 ++- .../pod/enclave_identity/zero_copy/mod.rs | 5 +- .../pod/enclave_identity/zero_copy/structs.rs | 100 +++++---- src/types/pod/mod.rs | 2 +- src/types/pod/tcb_info/mod.rs | 36 +-- src/types/pod/tcb_info/serialize.rs | 21 +- src/types/pod/tcb_info/tests.rs | 24 +- .../pod/tcb_info/zero_copy/conversion.rs | 21 +- src/types/pod/tcb_info/zero_copy/error.rs | 24 +- src/types/pod/tcb_info/zero_copy/iterators.rs | 6 +- src/types/pod/tcb_info/zero_copy/mod.rs | 12 +- src/types/pod/tcb_info/zero_copy/structs.rs | 209 ++++++++++++------ src/types/quote/cert_data.rs | 4 +- src/types/quote/header.rs | 5 +- src/types/report.rs | 5 +- src/types/tcb_info.rs | 2 +- src/utils.rs | 8 +- tests/common/mod.rs | 4 +- tests/tests.rs | 2 +- 28 files changed, 403 insertions(+), 285 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8108d4..a55aade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dcap-rs" license = "Apache-2.0" -version = "0.1.0" +version = "1.1.0" edition = "2021" description = "Intel Data Center Attestation Primitives (DCAP) Quote Verification Library (QVL) implemented in pure Rust." repository = "https://github.com/automata-network/dcap-rs" diff --git a/src/tdx.rs b/src/tdx.rs index 010a520..f4bf08d 100644 --- a/src/tdx.rs +++ b/src/tdx.rs @@ -77,41 +77,38 @@ pub fn check_for_relaunch( let mut relaunch_needed = false; let mut configuration_needed = false; - if qe_tcb_status != QeTcbStatus::OutOfDate - || qe_tcb_status != QeTcbStatus::OutOfDateConfigurationNeeded + if (qe_tcb_status != QeTcbStatus::OutOfDate + || qe_tcb_status != QeTcbStatus::OutOfDateConfigurationNeeded) + && !is_out_of_date(sgx_tcb_status) + && is_out_of_date(tdx_tcb_status) + && is_out_of_date(tdx_module_tcb_status) { - if !is_out_of_date(sgx_tcb_status) { - if is_out_of_date(tdx_tcb_status) { - if is_out_of_date(tdx_module_tcb_status) { - configuration_needed = is_configuration_needed(sgx_tcb_status) - || is_configuration_needed(tdx_tcb_status); - - let latest_tcb_level_tdx_svns = tcb_info - .tcb_levels - .first() - .unwrap() - .tcb - .tdx_tcb_components() - .unwrap(); - let tdx_module_version = td_report.tee_tcb_svn2[1]; - let tdx_module_svns = td_report.tee_tcb_svn2; - - if tdx_module_version == 0 { - if tdx_module_svns[0] >= latest_tcb_level_tdx_svns[0] - && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] - { - relaunch_needed = true; - } - } else { - let tdx_module_identity = - find_tdx_module_identity(tdx_module_version, tcb_info).unwrap(); - if tdx_module_svns[0] >= tdx_module_identity.tcb_levels[0].tcb.isvsvn - && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] - { - relaunch_needed = true; - } - } - } + configuration_needed = + is_configuration_needed(sgx_tcb_status) || is_configuration_needed(tdx_tcb_status); + + let latest_tcb_level_tdx_svns = tcb_info + .tcb_levels + .first() + .unwrap() + .tcb + .tdx_tcb_components() + .unwrap(); + let tdx_module_version = td_report.tee_tcb_svn2[1]; + let tdx_module_svns = td_report.tee_tcb_svn2; + + if tdx_module_version == 0 { + if tdx_module_svns[0] >= latest_tcb_level_tdx_svns[0] + && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] + { + relaunch_needed = true; + } + } else { + let tdx_module_identity = + find_tdx_module_identity(tdx_module_version, tcb_info).unwrap(); + if tdx_module_svns[0] >= tdx_module_identity.tcb_levels[0].tcb.isvsvn + && tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2] + { + relaunch_needed = true; } } } diff --git a/src/types/collateral.rs b/src/types/collateral.rs index a66cd18..cb32bed 100644 --- a/src/types/collateral.rs +++ b/src/types/collateral.rs @@ -1,12 +1,15 @@ #[cfg(feature = "zero-copy")] use crate::utils::cert_chain_processor; -use crate::utils::{cert_chain, crl}; use crate::utils::keccak; +use crate::utils::{cert_chain, crl}; use anyhow::Result; use serde::{Deserialize, Serialize}; #[cfg(not(feature = "zero-copy"))] use x509_cert::certificate::CertificateInner; -use x509_cert::{crl::CertificateList, der::{Decode, Encode}}; +use x509_cert::{ + crl::CertificateList, + der::{Decode, Encode}, +}; use super::{enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature}; diff --git a/src/types/enclave_identity.rs b/src/types/enclave_identity.rs index e12ef74..a1e22ac 100644 --- a/src/types/enclave_identity.rs +++ b/src/types/enclave_identity.rs @@ -151,7 +151,7 @@ impl EnclaveIdentity { self.tcb_levels .iter() .find(|level| level.tcb.isvsvn <= isv_svn) - .map(|level| level.tcb_status.clone()) + .map(|level| level.tcb_status) .unwrap_or(QeTcbStatus::Unspecified) } diff --git a/src/types/mod.rs b/src/types/mod.rs index 9931971..06731ae 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -61,7 +61,7 @@ impl VerifiedOutput { let mut offset = 11usize; let quote_body_type = u16::from_be_bytes(quote_body_type); - let quote_body = match quote_body_type{ + let quote_body = match quote_body_type { 1 => { let raw_quote_body: [u8; ENCLAVE_REPORT_LEN] = slice [offset..offset + ENCLAVE_REPORT_LEN] diff --git a/src/types/pod/enclave_identity/mod.rs b/src/types/pod/enclave_identity/mod.rs index 5a1772f..b4ae521 100644 --- a/src/types/pod/enclave_identity/mod.rs +++ b/src/types/pod/enclave_identity/mod.rs @@ -18,13 +18,13 @@ mod tests; #[derive(Copy, Clone, Debug, Pod, Zeroable)] // Removed Default, not needed for bytemuck if zeroed pub struct QeTcbLevelPodHeader { pub isvsvn: u16, - pub tcb_status: u8, // Represents QeTcbStatus enum + pub tcb_status: u8, // Represents QeTcbStatus enum pub _padding0: [u8; 5], // Align to 8 for tcb_date_timestamp pub tcb_date_timestamp: i64, pub advisory_ids_count: u32, pub advisory_ids_lengths_array_len: u32, // Total byte length of the array of u16 lengths - pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings - pub _padding1: [u8; 4], // Ensure struct size is multiple of alignment (8) + pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings + pub _padding1: [u8; 4], // Ensure struct size is multiple of alignment (8) } /// Main header for Enclave Identity. @@ -33,7 +33,7 @@ pub struct QeTcbLevelPodHeader { #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] // Removed Default pub struct EnclaveIdentityHeader { - pub id: u8, // Represents EnclaveType enum (e.g., 0: Qe, 1: Qve, 2: TdQe) + pub id: u8, // Represents EnclaveType enum (e.g., 0: Qe, 1: Qve, 2: TdQe) pub _padding_id: [u8; 3], // Align to 4 for version pub version: u32, pub issue_date_timestamp: i64, @@ -47,26 +47,25 @@ pub struct EnclaveIdentityHeader { pub attributes_hex: [u8; 32], pub attributes_mask_hex: [u8; 32], pub mrsigner_hex: [u8; 64], - + pub tcb_levels_count: u32, pub tcb_levels_total_payload_len: u32, // Total byte length for all QeTcbLevelPodHeader sections + their payloads - // Ensure total size is a multiple of 8 (max alignment of fields) - // Current size before this padding: - // id (1) + pad_id (3) = 4 - // version (4) = 4 - // issue_date (8) = 8 - // next_update (8) = 8 - // tcb_eval_num (4) = 4 - // isvprodid (2) + _final_padding (2) = 4 - // miscselect (8) = 8 - // miscselect_mask (8) = 8 - // attributes (32) = 32 - // attributes_mask (32) = 32 - // mrsigner (64) = 64 - // tcb_levels_count (4) = 4 - // tcb_levels_total_payload_len (4) = 4 - // Total = 4+4+8+8+4+4+8+8+32+32+64+4+4 = 184 - + // Ensure total size is a multiple of 8 (max alignment of fields) + // Current size before this padding: + // id (1) + pad_id (3) = 4 + // version (4) = 4 + // issue_date (8) = 8 + // next_update (8) = 8 + // tcb_eval_num (4) = 4 + // isvprodid (2) + _final_padding (2) = 4 + // miscselect (8) = 8 + // miscselect_mask (8) = 8 + // attributes (32) = 32 + // attributes_mask (32) = 32 + // mrsigner (64) = 64 + // tcb_levels_count (4) = 4 + // tcb_levels_total_payload_len (4) = 4 + // Total = 4+4+8+8+4+4+8+8+32+32+64+4+4 = 184 } /// The top-level POD structure for Enclave Identity and its signature. @@ -84,7 +83,7 @@ pub struct EnclaveIdentityPod { #[cfg(test)] mod pod_layout_tests { use super::*; - use core::mem::{size_of, align_of}; + use core::mem::{align_of, size_of}; #[test] fn check_qe_tcb_level_pod_header_layout() { diff --git a/src/types/pod/enclave_identity/serialize.rs b/src/types/pod/enclave_identity/serialize.rs index 515fde3..8f6a48c 100644 --- a/src/types/pod/enclave_identity/serialize.rs +++ b/src/types/pod/enclave_identity/serialize.rs @@ -1,7 +1,5 @@ // src/types/pod/enclave_identity/serialize.rs -#![cfg(feature = "full")] - use super::zero_copy::{EnclaveIdentityZeroCopy, conversion::enclave_identity_from_zero_copy}; use super::{EnclaveIdentityHeader, QeTcbLevelPodHeader}; use crate::types::enclave_identity::{EnclaveIdentity, EnclaveType}; @@ -84,7 +82,7 @@ impl SerializedEnclaveIdentity { header.attributes_hex = hex_chars_to_fixed_bytes::<32>(&rust_ei.attributes); header.attributes_mask_hex = hex_chars_to_fixed_bytes::<32>(&rust_ei.attributes_mask); header.mrsigner_hex = hex_chars_to_fixed_bytes::<64>(&rust_ei.mrsigner); - + let tcb_levels_payload_start_offset = current_payload_offset; header.tcb_levels_count = rust_ei.tcb_levels.len() as u32; @@ -110,8 +108,7 @@ impl SerializedEnclaveIdentity { } qe_tcb_level_header.advisory_ids_lengths_array_len = advisory_id_lengths_bytes.len() as u32; - qe_tcb_level_header.advisory_ids_data_total_len = - advisory_id_data_bytes.len() as u32; + qe_tcb_level_header.advisory_ids_data_total_len = advisory_id_data_bytes.len() as u32; payload_bytes.extend_from_slice(bytemuck::bytes_of(&qe_tcb_level_header)); current_payload_offset += mem::size_of::(); @@ -121,11 +118,11 @@ impl SerializedEnclaveIdentity { payload_bytes.extend_from_slice(&advisory_id_data_bytes); current_payload_offset += advisory_id_data_bytes.len(); - + // Add padding for the *next* QeTcbLevelPodHeader let padding_added = append_padding_to_align( &mut payload_bytes, - mem::align_of::() // Align to 8 + mem::align_of::(), // Align to 8 ); current_payload_offset += padding_added; } @@ -146,9 +143,8 @@ pub fn serialize_enclave_identity_pod( signature: &[u8; 64], ) -> Vec { let header_bytes = bytemuck::bytes_of(&serialized_ei.header); - let mut pod_bytes = Vec::with_capacity( - signature.len() + header_bytes.len() + serialized_ei.payload.len(), - ); + let mut pod_bytes = + Vec::with_capacity(signature.len() + header_bytes.len() + serialized_ei.payload.len()); pod_bytes.extend_from_slice(signature); pod_bytes.extend_from_slice(header_bytes); pod_bytes.extend_from_slice(&serialized_ei.payload); @@ -157,7 +153,9 @@ pub fn serialize_enclave_identity_pod( /// Parses a byte slice representing an EnclaveIdentityPod into an application-level EnclaveIdentity and the signature. /// Expects bytes in the layout: signature | EnclaveIdentityHeader | payload. -pub fn parse_enclave_identity_pod_bytes(pod_bytes: &[u8]) -> Result<(EnclaveIdentity, [u8; 64]), String> { +pub fn parse_enclave_identity_pod_bytes( + pod_bytes: &[u8], +) -> Result<(EnclaveIdentity, [u8; 64]), String> { let signature_len = 64; let header_len = mem::size_of::(); let min_len = signature_len + header_len; @@ -186,8 +184,12 @@ pub fn parse_enclave_identity_pod_bytes(pod_bytes: &[u8]) -> Result<(EnclaveIden let ei_zero_copy_view = EnclaveIdentityZeroCopy::from_bytes(ei_header_and_payload_bytes) .map_err(|e| format!("Failed to create EnclaveIdentityZeroCopy view: {:?}", e))?; - let rust_ei = enclave_identity_from_zero_copy(&ei_zero_copy_view) - .map_err(|e| format!("Failed to convert EnclaveIdentityZeroCopy to EnclaveIdentity: {:?}", e))?; + let rust_ei = enclave_identity_from_zero_copy(&ei_zero_copy_view).map_err(|e| { + format!( + "Failed to convert EnclaveIdentityZeroCopy to EnclaveIdentity: {:?}", + e + ) + })?; Ok((rust_ei, signature)) } diff --git a/src/types/pod/enclave_identity/zero_copy/conversion.rs b/src/types/pod/enclave_identity/zero_copy/conversion.rs index 80600ff..bfe005d 100644 --- a/src/types/pod/enclave_identity/zero_copy/conversion.rs +++ b/src/types/pod/enclave_identity/zero_copy/conversion.rs @@ -1,7 +1,5 @@ // src/types/pod/enclave_identity/zero_copy/conversion.rs -#![cfg(feature = "full")] - use super::error::ZeroCopyError; use super::structs::{EnclaveIdentityZeroCopy, QeTcbLevelZeroCopy}; use crate::types::enclave_identity::{ @@ -41,9 +39,7 @@ fn qe_tcb_status_from_byte(byte: u8) -> Result { } } -pub fn qe_tcb_level_from_zero_copy( - view: &QeTcbLevelZeroCopy, -) -> Result { +pub fn qe_tcb_level_from_zero_copy(view: &QeTcbLevelZeroCopy) -> Result { let mut advisory_ids_vec = Vec::new(); if view.advisory_ids_count() > 0 { for adv_id_res in view.advisory_ids() { diff --git a/src/types/pod/enclave_identity/zero_copy/error.rs b/src/types/pod/enclave_identity/zero_copy/error.rs index 024f898..d1823a8 100644 --- a/src/types/pod/enclave_identity/zero_copy/error.rs +++ b/src/types/pod/enclave_identity/zero_copy/error.rs @@ -21,10 +21,17 @@ impl ZeroCopyError { impl core::fmt::Display for ZeroCopyError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - ZeroCopyError::InvalidSliceLength => write!(f, "Invalid slice length encountered during zero-copy parsing"), - ZeroCopyError::InvalidOffset => write!(f, "Invalid offset calculated during zero-copy parsing"), + ZeroCopyError::InvalidSliceLength => write!( + f, + "Invalid slice length encountered during zero-copy parsing" + ), + ZeroCopyError::InvalidOffset => { + write!(f, "Invalid offset calculated during zero-copy parsing") + }, ZeroCopyError::InvalidEnumValue => write!(f, "Invalid enum value encountered"), - ZeroCopyError::InvalidUtf8 => write!(f, "Invalid UTF-8 sequence encountered in string data"), + ZeroCopyError::InvalidUtf8 => { + write!(f, "Invalid UTF-8 sequence encountered in string data") + }, ZeroCopyError::BytemuckError(e) => write!(f, "Bytemuck PodCastError: {:?}", e), } } diff --git a/src/types/pod/enclave_identity/zero_copy/iterators.rs b/src/types/pod/enclave_identity/zero_copy/iterators.rs index 5679b44..9547ee6 100644 --- a/src/types/pod/enclave_identity/zero_copy/iterators.rs +++ b/src/types/pod/enclave_identity/zero_copy/iterators.rs @@ -8,7 +8,7 @@ use core::{mem, str}; // Helper from structs.rs - consider moving to a shared util #[inline] -fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { +fn cast_slice(slice: &[u8]) -> Result<&T, ZeroCopyError> { bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) } @@ -53,14 +53,15 @@ impl<'a> Iterator for AdvisoryIdIter<'a> { self.current_idx = self.count; // Exhaust iterator return Some(Err(ZeroCopyError::InvalidSliceLength)); } - + let len_bytes_slice = self.lengths_payload.get(len_offset..len_bytes_end)?; let len = u16::from_le_bytes(match len_bytes_slice.try_into() { Ok(arr) => arr, - Err(_) => { // Should not happen if previous check passed + Err(_) => { + // Should not happen if previous check passed self.current_idx = self.count; return Some(Err(ZeroCopyError::InvalidSliceLength)); - } + }, }) as usize; let data_end = self.current_data_offset.checked_add(len)?; @@ -109,10 +110,11 @@ impl<'a> Iterator for QeTcbLevelIter<'a> { let header_size = mem::size_of::(); let header_slice_end = match self.current_offset.checked_add(header_size) { Some(end) => end, - None => { // Offset calculation overflow + None => { + // Offset calculation overflow self.current_idx = self.count; return Some(Err(ZeroCopyError::InvalidOffset)); - } + }, }; if header_slice_end > self.full_payload.len() { @@ -126,7 +128,7 @@ impl<'a> Iterator for QeTcbLevelIter<'a> { Err(e) => { self.current_idx = self.count; return Some(Err(e)); - } + }, }; let current_item_internal_payload_start = header_slice_end; @@ -136,19 +138,21 @@ impl<'a> Iterator for QeTcbLevelIter<'a> { let actual_item_payload_len = match adv_ids_lengths_len.checked_add(adv_ids_data_len) { Some(len) => len, - None => { // Offset calculation overflow + None => { + // Offset calculation overflow self.current_idx = self.count; return Some(Err(ZeroCopyError::InvalidOffset)); - } + }, }; - + let item_payload_actual_end = match current_item_internal_payload_start.checked_add(actual_item_payload_len) { Some(end) => end, - None => { // Offset calculation overflow + None => { + // Offset calculation overflow self.current_idx = self.count; return Some(Err(ZeroCopyError::InvalidOffset)); - } + }, }; if item_payload_actual_end > self.full_payload.len() { diff --git a/src/types/pod/enclave_identity/zero_copy/mod.rs b/src/types/pod/enclave_identity/zero_copy/mod.rs index 760027c..1517f21 100644 --- a/src/types/pod/enclave_identity/zero_copy/mod.rs +++ b/src/types/pod/enclave_identity/zero_copy/mod.rs @@ -1,14 +1,15 @@ // src/types/pod/enclave_identity/zero_copy/mod.rs pub mod error; -pub mod structs; pub mod iterators; +pub mod structs; #[cfg(feature = "full")] pub mod conversion; pub use error::ZeroCopyError; // error.rs is a submodule -pub use structs::{ // structs.rs is a submodule +pub use structs::{ + // structs.rs is a submodule EnclaveIdentityZeroCopy, QeTcbLevelZeroCopy, }; diff --git a/src/types/pod/enclave_identity/zero_copy/structs.rs b/src/types/pod/enclave_identity/zero_copy/structs.rs index 92ad469..7239c7a 100644 --- a/src/types/pod/enclave_identity/zero_copy/structs.rs +++ b/src/types/pod/enclave_identity/zero_copy/structs.rs @@ -1,15 +1,13 @@ // src/types/pod/enclave_identity/zero_copy/structs.rs use super::error::ZeroCopyError; -use super::iterators::{QeTcbLevelIter, AdvisoryIdIter}; // Will define iterators later -use crate::types::pod::enclave_identity::{ - EnclaveIdentityHeader, QeTcbLevelPodHeader, -}; +use super::iterators::{AdvisoryIdIter, QeTcbLevelIter}; // Will define iterators later +use crate::types::pod::enclave_identity::{EnclaveIdentityHeader, QeTcbLevelPodHeader}; use bytemuck::Pod; // Helper to cast slices (can be moved to a shared util if used more widely) #[inline] -fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { +fn cast_slice(slice: &[u8]) -> Result<&T, ZeroCopyError> { bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) } @@ -28,15 +26,17 @@ impl<'a> EnclaveIdentityZeroCopy<'a> { if bytes.len() < core::mem::size_of::() { return Err(ZeroCopyError::InvalidSliceLength); } - let (header_bytes, main_payload) = bytes.split_at(core::mem::size_of::()); + let (header_bytes, main_payload) = + bytes.split_at(core::mem::size_of::()); let header: &EnclaveIdentityHeader = cast_slice(header_bytes)?; let levels_len = header.tcb_levels_total_payload_len as usize; - let tcb_levels_section_payload = main_payload.get(0..levels_len) + let tcb_levels_section_payload = main_payload + .get(0..levels_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; - + if levels_len > main_payload.len() { - return Err(ZeroCopyError::InvalidSliceLength); + return Err(ZeroCopyError::InvalidSliceLength); } Ok(Self { @@ -46,45 +46,59 @@ impl<'a> EnclaveIdentityZeroCopy<'a> { } // --- Direct Header Accessors --- - pub fn id_byte(&self) -> u8 { self.header.id } - pub fn version(&self) -> u32 { self.header.version } - pub fn issue_date_timestamp(&self) -> i64 { self.header.issue_date_timestamp } - pub fn next_update_timestamp(&self) -> i64 { self.header.next_update_timestamp } - pub fn tcb_evaluation_data_number(&self) -> u32 { self.header.tcb_evaluation_data_number } - pub fn isvprodid(&self) -> u16 { self.header.isvprodid } - pub fn miscselect_bytes(&self) -> [u8; 4] { - hex::decode(&self.header.miscselect_hex) + pub fn id_byte(&self) -> u8 { + self.header.id + } + pub fn version(&self) -> u32 { + self.header.version + } + pub fn issue_date_timestamp(&self) -> i64 { + self.header.issue_date_timestamp + } + pub fn next_update_timestamp(&self) -> i64 { + self.header.next_update_timestamp + } + pub fn tcb_evaluation_data_number(&self) -> u32 { + self.header.tcb_evaluation_data_number + } + pub fn isvprodid(&self) -> u16 { + self.header.isvprodid + } + pub fn miscselect_bytes(&self) -> [u8; 4] { + hex::decode(self.header.miscselect_hex) .expect("Failed to decode miscselect_hex") .try_into() .expect("Failed to convert miscselect_hex to byte array") - } - pub fn miscselect_mask_bytes(&self) -> [u8; 4] { - hex::decode(&self.header.miscselect_mask_hex) + } + pub fn miscselect_mask_bytes(&self) -> [u8; 4] { + hex::decode(self.header.miscselect_mask_hex) .expect("Failed to decode miscselect_mask_hex") .try_into() .expect("Failed to convert miscselect_mask_hex to byte array") - } - pub fn attributes_bytes(&self) -> [u8; 16] { - hex::decode(&self.header.attributes_hex) + } + pub fn attributes_bytes(&self) -> [u8; 16] { + hex::decode(self.header.attributes_hex) .expect("Failed to decode attributes_hex") .try_into() .expect("Failed to convert attributes_hex to byte array") - } - pub fn attributes_mask_bytes(&self) -> [u8; 16] { - hex::decode(&self.header.attributes_mask_hex) + } + pub fn attributes_mask_bytes(&self) -> [u8; 16] { + hex::decode(self.header.attributes_mask_hex) .expect("Failed to decode attributes_mask_hex") .try_into() .expect("Failed to convert attributes_mask_hex to byte array") - } - pub fn mrsigner_bytes(&self) -> [u8; 32] { - hex::decode(&self.header.mrsigner_hex) + } + pub fn mrsigner_bytes(&self) -> [u8; 32] { + hex::decode(self.header.mrsigner_hex) .expect("Failed to decode mrsigner_hex") .try_into() .expect("Failed to convert mrsigner_hex to byte array") - } - + } + // --- Parsed/Structured Accessors --- - pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels_count(&self) -> u32 { + self.header.tcb_levels_count + } pub fn tcb_levels(&self) -> QeTcbLevelIter<'a> { QeTcbLevelIter::new( self.tcb_levels_section_payload, @@ -107,7 +121,9 @@ impl<'a> QeTcbLevelZeroCopy<'a> { let lengths_len = header.advisory_ids_lengths_array_len as usize; let data_len = header.advisory_ids_data_total_len as usize; - let total_adv_payload_len = lengths_len.checked_add(data_len).ok_or(ZeroCopyError::InvalidOffset)?; + let total_adv_payload_len = lengths_len + .checked_add(data_len) + .ok_or(ZeroCopyError::InvalidOffset)?; if total_adv_payload_len > payload.len() { return Err(ZeroCopyError::InvalidSliceLength); } @@ -115,14 +131,22 @@ impl<'a> QeTcbLevelZeroCopy<'a> { Ok(Self { header, advisory_ids_lengths_payload: &payload[..lengths_len], - advisory_ids_data_payload: &payload[lengths_len .. lengths_len + data_len], + advisory_ids_data_payload: &payload[lengths_len..lengths_len + data_len], }) } - pub fn isvsvn(&self) -> u16 { self.header.isvsvn } - pub fn tcb_status_byte(&self) -> u8 { self.header.tcb_status } - pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } - pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + pub fn isvsvn(&self) -> u16 { + self.header.isvsvn + } + pub fn tcb_status_byte(&self) -> u8 { + self.header.tcb_status + } + pub fn tcb_date_timestamp(&self) -> i64 { + self.header.tcb_date_timestamp + } + pub fn advisory_ids_count(&self) -> u32 { + self.header.advisory_ids_count + } pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { AdvisoryIdIter::new( diff --git a/src/types/pod/mod.rs b/src/types/pod/mod.rs index 7e2f229..4f343b4 100644 --- a/src/types/pod/mod.rs +++ b/src/types/pod/mod.rs @@ -1,2 +1,2 @@ +pub mod enclave_identity; pub mod tcb_info; -pub mod enclave_identity; \ No newline at end of file diff --git a/src/types/pod/tcb_info/mod.rs b/src/types/pod/tcb_info/mod.rs index 6a6e636..1e432ff 100644 --- a/src/types/pod/tcb_info/mod.rs +++ b/src/types/pod/tcb_info/mod.rs @@ -43,12 +43,12 @@ pub struct TdxTcbLevelHeader { #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct TdxModuleIdentityHeader { - pub mrsigner_hex: [u8; 96], // Hex string - pub attributes_hex: [u8; 16], // Hex string + pub mrsigner_hex: [u8; 96], // Hex string + pub attributes_hex: [u8; 16], // Hex string pub attributes_mask_hex: [u8; 16], // Hex string - pub id_len: u8, // Length of the ID string (e.g., "TDX_01") + pub id_len: u8, // Length of the ID string (e.g., "TDX_01") pub _padding0: [u8; 7], - pub tcb_levels_count: u32, // Number of TdxTcbLevelHeader + payload sections + pub tcb_levels_count: u32, // Number of TdxTcbLevelHeader + payload sections pub tcb_levels_total_payload_len: u32, // Total byte length for all TdxTcbLevelHeader sections for this identity } @@ -57,8 +57,8 @@ pub struct TdxModuleIdentityHeader { #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct TdxModulePodData { - pub mrsigner_hex: [u8; 96], // Hex string - pub attributes_hex: [u8; 16], // Hex string + pub mrsigner_hex: [u8; 96], // Hex string + pub attributes_hex: [u8; 16], // Hex string pub attributes_mask_hex: [u8; 16], // Hex string } @@ -84,7 +84,7 @@ pub struct TcbLevelHeader { pub tdx_components_strings_total_len: u32, // Sum for TDX components, if present (0 otherwise) pub advisory_ids_count: u32, pub advisory_ids_lengths_array_len: u32, // Total byte length of the array of u16 lengths - pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings + pub advisory_ids_data_total_len: u32, // Total byte length of concatenated advisory ID strings pub _final_padding: [u8; 4], } @@ -95,27 +95,27 @@ pub struct TcbLevelHeader { #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable, Default)] pub struct TcbInfoHeader { - pub id_type: [u8; 6], + pub id_type: [u8; 6], pub _pad_id_type: [u8; 2], - pub version: u32, + pub version: u32, pub _pad_version: [u8; 4], pub issue_date_timestamp: i64, pub next_update_timestamp: i64, - pub fmspc_hex: [u8; 12], - pub pce_id_hex: [u8; 4], - pub tcb_type: u8, + pub fmspc_hex: [u8; 12], + pub pce_id_hex: [u8; 4], + pub tcb_type: u8, pub _pad_tcb_type: [u8; 3], - pub tcb_evaluation_data_number: u32, - - pub tdx_module_present: u8, + pub tcb_evaluation_data_number: u32, + + pub tdx_module_present: u8, pub _pad_tdx_module_present: [u8; 3], - pub tdx_module_data_len: u32, + pub tdx_module_data_len: u32, pub tdx_module_identities_count: u32, - pub tdx_module_identities_total_payload_len: u32, + pub tdx_module_identities_total_payload_len: u32, pub tcb_levels_count: u32, - pub tcb_levels_total_payload_len: u32, + pub tcb_levels_total_payload_len: u32, } // Size = 144 bytes, Alignment = 8 bytes diff --git a/src/types/pod/tcb_info/serialize.rs b/src/types/pod/tcb_info/serialize.rs index aefb68c..127151e 100644 --- a/src/types/pod/tcb_info/serialize.rs +++ b/src/types/pod/tcb_info/serialize.rs @@ -101,8 +101,7 @@ impl SerializedTcbInfo { let mut tdx_tcb_level_header = TdxTcbLevelHeader::zeroed(); tdx_tcb_level_header.tcb_isvsvn = tdx_tcb_level.tcb.isvsvn; tdx_tcb_level_header.tcb_status = tdx_tcb_level.tcb_status as u8; - tdx_tcb_level_header.tcb_date_timestamp = - tdx_tcb_level.tcb_date.timestamp(); + tdx_tcb_level_header.tcb_date_timestamp = tdx_tcb_level.tcb_date.timestamp(); let mut advisory_id_lengths_bytes = Vec::new(); let mut advisory_id_data_bytes = Vec::new(); @@ -131,7 +130,7 @@ impl SerializedTcbInfo { // Add padding for the *next* TdxTcbLevelHeader in this sub-list append_padding_to_align( &mut tdx_tcb_levels_payload_for_identity, - mem::align_of::() // Align to 8 + mem::align_of::(), // Align to 8 ); } identity_header.tcb_levels_total_payload_len = @@ -147,17 +146,17 @@ impl SerializedTcbInfo { // as TdxTcbLevelHeader requires 8-byte alignment. let padding_for_tdx_tcb_level_list = append_padding_to_align( &mut payload_bytes, - mem::align_of::() // Align to 8 + mem::align_of::(), // Align to 8 ); current_payload_offset += padding_for_tdx_tcb_level_list; payload_bytes.extend_from_slice(&tdx_tcb_levels_payload_for_identity); current_payload_offset += tdx_tcb_levels_payload_for_identity.len(); - + // Add padding for the *next* TdxModuleIdentityHeader let padding_added_for_next_identity = append_padding_to_align( &mut payload_bytes, - mem::align_of::() // Align to 4 + mem::align_of::(), // Align to 4 ); current_payload_offset += padding_added_for_next_identity; } @@ -188,8 +187,8 @@ impl SerializedTcbInfo { .component_type .as_deref() .unwrap_or(""); - comp_header_ref.category_len = cat_str.as_bytes().len() as u8; - comp_header_ref.component_type_len = type_str.as_bytes().len() as u8; + comp_header_ref.category_len = cat_str.len() as u8; + comp_header_ref.component_type_len = type_str.len() as u8; sgx_components_payload_strings.extend_from_slice(cat_str.as_bytes()); sgx_components_payload_strings.extend_from_slice(type_str.as_bytes()); } @@ -200,8 +199,8 @@ impl SerializedTcbInfo { comp_header_ref.cpusvn = tdx_comps_v3[i].svn; let cat_str = tdx_comps_v3[i].category.as_deref().unwrap_or(""); let type_str = tdx_comps_v3[i].component_type.as_deref().unwrap_or(""); - comp_header_ref.category_len = cat_str.as_bytes().len() as u8; - comp_header_ref.component_type_len = type_str.as_bytes().len() as u8; + comp_header_ref.category_len = cat_str.len() as u8; + comp_header_ref.component_type_len = type_str.len() as u8; tdx_components_payload_strings_for_level .extend_from_slice(cat_str.as_bytes()); tdx_components_payload_strings_for_level @@ -265,7 +264,7 @@ impl SerializedTcbInfo { // Add padding for the *next* TcbLevelHeader let padding_added_for_next_tcb_level = append_padding_to_align( &mut payload_bytes, - mem::align_of::() // Align to 8 + mem::align_of::(), // Align to 8 ); current_payload_offset += padding_added_for_next_tcb_level; } diff --git a/src/types/pod/tcb_info/tests.rs b/src/types/pod/tcb_info/tests.rs index bf92379..90cf847 100644 --- a/src/types/pod/tcb_info/tests.rs +++ b/src/types/pod/tcb_info/tests.rs @@ -35,7 +35,7 @@ fn test_tcb_v2_sgx_bytemuck() { // Integrity Check let original_tcb_info_hash = - Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + Sha256::digest(tcb_info_and_signature.tcb_info_raw.get().as_bytes()); let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) .expect("Failed to serialize parsed TcbInfoV2 to JSON string"); let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); @@ -66,10 +66,7 @@ fn test_tcb_v2_sgx_bytemuck() { tcb_info_zero_copy.next_update_timestamp(), original_tcb_info.next_update.timestamp() ); - assert_eq!( - tcb_info_zero_copy.fmspc(), - original_tcb_info.fmspc_bytes() - ); + assert_eq!(tcb_info_zero_copy.fmspc(), original_tcb_info.fmspc_bytes()); assert_eq!( tcb_info_zero_copy.pce_id(), original_tcb_info.pce_id_bytes() @@ -173,7 +170,7 @@ fn test_tcb_v3_sgx_bytemuck() { // Integrity Check let original_tcb_info_hash = - Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + Sha256::digest(tcb_info_and_signature.tcb_info_raw.get().as_bytes()); let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) .expect("Failed to serialize parsed TcbInfoV3 SGX to JSON string"); let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); @@ -200,10 +197,7 @@ fn test_tcb_v3_sgx_bytemuck() { tcb_info_zero_copy.next_update_timestamp(), original_tcb_info.next_update.timestamp() ); - assert_eq!( - tcb_info_zero_copy.fmspc(), - original_tcb_info.fmspc_bytes() - ); + assert_eq!(tcb_info_zero_copy.fmspc(), original_tcb_info.fmspc_bytes()); assert_eq!( tcb_info_zero_copy.pce_id(), original_tcb_info.pce_id_bytes() @@ -329,7 +323,7 @@ fn test_tcb_v3_tdx_bytemuck() { // Integrity Check let original_tcb_info_hash = - Sha256::digest(&tcb_info_and_signature.tcb_info_raw.get().as_bytes()); + Sha256::digest(tcb_info_and_signature.tcb_info_raw.get().as_bytes()); let parsed_tcb_info_string = serde_json::to_string(&parsed_tcb_info) .expect("Failed to serialize parsed TcbInfoV3 SGX to JSON string"); let parsed_tcb_info_hash = Sha256::digest(parsed_tcb_info_string.as_bytes()); @@ -356,10 +350,7 @@ fn test_tcb_v3_tdx_bytemuck() { tcb_info_zero_copy.next_update_timestamp(), original_tcb_info.next_update.timestamp() ); - assert_eq!( - tcb_info_zero_copy.fmspc(), - original_tcb_info.fmspc_bytes() - ); + assert_eq!(tcb_info_zero_copy.fmspc(), original_tcb_info.fmspc_bytes()); assert_eq!( tcb_info_zero_copy.pce_id(), original_tcb_info.pce_id_bytes() @@ -407,8 +398,7 @@ fn test_tcb_v3_tdx_bytemuck() { tdx_module_identity.expect("Failed to get TDX module identity from view"); assert_eq!( tdx_module_identity.mrsigner(), - original_tdx_module_identities[i] - .mrsigner_bytes() + original_tdx_module_identities[i].mrsigner_bytes() ); assert_eq!( tdx_module_identity.attributes(), diff --git a/src/types/pod/tcb_info/zero_copy/conversion.rs b/src/types/pod/tcb_info/zero_copy/conversion.rs index 2546c19..4e00f8c 100644 --- a/src/types/pod/tcb_info/zero_copy/conversion.rs +++ b/src/types/pod/tcb_info/zero_copy/conversion.rs @@ -36,25 +36,22 @@ pub fn tcb_info_from_zero_copy(view: &TcbInfoZeroCopy) -> Result = match view.tdx_module() { - Some(tdx_mod_view) => Some(TdxModule { - mrsigner: zero_copy_bytes_to_string(&tdx_mod_view.data.mrsigner_hex), - attributes: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_hex), - attributes_mask: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_mask_hex), - }), - None => None, - }; + let tdx_module: Option = view.tdx_module().map(|tdx_mod_view| TdxModule { + mrsigner: zero_copy_bytes_to_string(&tdx_mod_view.data.mrsigner_hex), + attributes: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_hex), + attributes_mask: zero_copy_bytes_to_string(&tdx_mod_view.data.attributes_mask_hex), + }); let mut tdx_module_identities_vec = Vec::new(); if view.tdx_module_identities_count() > 0 { @@ -75,7 +72,7 @@ pub fn tcb_info_from_zero_copy(view: &TcbInfoZeroCopy) -> Result Result Self { match e { - bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => ZeroCopyError::AlignmentError, + bytemuck::PodCastError::TargetAlignmentGreaterAndInputNotAligned => { + ZeroCopyError::AlignmentError + }, bytemuck::PodCastError::OutputSliceWouldHaveSlop => ZeroCopyError::OutputWouldHaveSlop, bytemuck::PodCastError::SizeMismatch => ZeroCopyError::InvalidSliceLength, // Or a more specific SizeMismatch variant _ => ZeroCopyError::PodCastError, @@ -37,12 +39,22 @@ impl core::fmt::Display for ZeroCopyError { ZeroCopyError::InvalidOffset => "Invalid offset calculation or out of bounds", ZeroCopyError::DataNotPresent => "Expected data not present where indicated", ZeroCopyError::InvalidEnumValue => "Invalid value for enum conversion", - ZeroCopyError::AlignmentError => "Input slice is not sufficiently aligned for the target type", - ZeroCopyError::OutputWouldHaveSlop => "Output slice would have uninitialized trailing padding bytes", + ZeroCopyError::AlignmentError => { + "Input slice is not sufficiently aligned for the target type" + }, + ZeroCopyError::OutputWouldHaveSlop => { + "Output slice would have uninitialized trailing padding bytes" + }, ZeroCopyError::PodCastError => "A general bytemuck PodCastError occurred", - ZeroCopyError::UnexpectedSgxComponentCount => "Unexpected number of SGX TCB components encountered", - ZeroCopyError::NoMatchingSgxTcbLevel => "No matching SGX TCB level found for the provided PCK extension", - ZeroCopyError::MissingTdxComponentsInTcbInfo => "TDX TCB components missing in TCB Info for a TDX quote", + ZeroCopyError::UnexpectedSgxComponentCount => { + "Unexpected number of SGX TCB components encountered" + }, + ZeroCopyError::NoMatchingSgxTcbLevel => { + "No matching SGX TCB level found for the provided PCK extension" + }, + ZeroCopyError::MissingTdxComponentsInTcbInfo => { + "TDX TCB components missing in TCB Info for a TDX quote" + }, }; write!(f, "{}", msg) } diff --git a/src/types/pod/tcb_info/zero_copy/iterators.rs b/src/types/pod/tcb_info/zero_copy/iterators.rs index 3ac1e34..80be817 100644 --- a/src/types/pod/tcb_info/zero_copy/iterators.rs +++ b/src/types/pod/tcb_info/zero_copy/iterators.rs @@ -14,7 +14,7 @@ use core::mem; // For align_of // Helper from structs.rs - consider moving to a shared util if this pattern repeats more #[inline] -fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { +fn cast_slice(slice: &[u8]) -> Result<&T, ZeroCopyError> { bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) } @@ -130,7 +130,7 @@ impl<'a> Iterator for TdxTcbLevelIter<'a> { Some(end) => end, None => return Some(Err(ZeroCopyError::InvalidOffset)), }; - + if item_payload_actual_end > self.full_payload.len() { return Some(Err(ZeroCopyError::InvalidSliceLength)); } @@ -192,7 +192,7 @@ impl<'a> Iterator for TdxModuleIdentityIter<'a> { let current_item_internal_offset = header_end; let alignment = mem::align_of::(); - let offset = header.id_len as usize + (alignment - 1) & !(alignment - 1); + let offset = (header.id_len as usize + (alignment - 1)) & !(alignment - 1); let actual_item_payload_len = (offset) .checked_add(header.tcb_levels_total_payload_len as usize) .ok_or(ZeroCopyError::InvalidOffset); diff --git a/src/types/pod/tcb_info/zero_copy/mod.rs b/src/types/pod/tcb_info/zero_copy/mod.rs index ecbbc12..a5a80f1 100644 --- a/src/types/pod/tcb_info/zero_copy/mod.rs +++ b/src/types/pod/tcb_info/zero_copy/mod.rs @@ -1,8 +1,8 @@ // src/types/pod/tcb_info/zero_copy/mod.rs pub mod error; -pub mod structs; pub mod iterators; +pub mod structs; pub mod utils; #[cfg(feature = "full")] @@ -10,13 +10,13 @@ pub mod conversion; pub use error::ZeroCopyError; pub use structs::{ - TcbInfoZeroCopy, - TdxModulePodDataZeroCopy, - TdxModuleIdentityZeroCopy, - TdxTcbLevelZeroCopy, - TcbLevelZeroCopy, TcbComponentZeroCopy, // Potentially export intermediate payload structs if they are part of the public API + TcbInfoZeroCopy, + TcbLevelZeroCopy, + TdxModuleIdentityZeroCopy, + TdxModulePodDataZeroCopy, + TdxTcbLevelZeroCopy, }; // Iterators might also be exported if users need to name their types explicitly. // pub use iterators::*; diff --git a/src/types/pod/tcb_info/zero_copy/structs.rs b/src/types/pod/tcb_info/zero_copy/structs.rs index f1591cb..610eb7b 100644 --- a/src/types/pod/tcb_info/zero_copy/structs.rs +++ b/src/types/pod/tcb_info/zero_copy/structs.rs @@ -3,8 +3,8 @@ use super::error::ZeroCopyError; use super::iterators::*; // Will define iterators later use crate::types::pod::tcb_info::{ - TcbInfoHeader, TdxModulePodData, TdxModuleIdentityHeader, TdxTcbLevelHeader, - TcbLevelHeader, TcbComponentHeader + TcbComponentHeader, TcbInfoHeader, TcbLevelHeader, TdxModuleIdentityHeader, TdxModulePodData, + TdxTcbLevelHeader, }; use bytemuck::Pod; use std::str::from_utf8; @@ -13,7 +13,7 @@ use std::str::from_utf8; // It's good practice to have this in a shared utility if used in multiple places, // but for now, keeping it here for self-containment of this module's direct needs. #[inline] -fn cast_slice<'a, T: Pod>(slice: &'a [u8]) -> Result<&'a T, ZeroCopyError> { +fn cast_slice(slice: &[u8]) -> Result<&T, ZeroCopyError> { bytemuck::try_from_bytes(slice).map_err(ZeroCopyError::from_bytemuck_error) } @@ -40,7 +40,8 @@ impl<'a> TcbInfoZeroCopy<'a> { let mut current_offset = 0; let tdx_module_data_payload = if header.tdx_module_present == 1 { let len = header.tdx_module_data_len as usize; - let slice = main_payload.get(current_offset..current_offset + len) + let slice = main_payload + .get(current_offset..current_offset + len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += len; Some(cast_slice(slice)?) @@ -49,17 +50,19 @@ impl<'a> TcbInfoZeroCopy<'a> { }; let identities_len = header.tdx_module_identities_total_payload_len as usize; - let tdx_module_identities_section_payload = main_payload.get(current_offset..current_offset + identities_len) + let tdx_module_identities_section_payload = main_payload + .get(current_offset..current_offset + identities_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += identities_len; let levels_len = header.tcb_levels_total_payload_len as usize; - let tcb_levels_section_payload = main_payload.get(current_offset..current_offset + levels_len) + let tcb_levels_section_payload = main_payload + .get(current_offset..current_offset + levels_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += levels_len; - + if current_offset > main_payload.len() { - return Err(ZeroCopyError::InvalidSliceLength); + return Err(ZeroCopyError::InvalidSliceLength); } Ok(Self { @@ -71,27 +74,48 @@ impl<'a> TcbInfoZeroCopy<'a> { } // --- Direct Header Accessors --- - pub fn id_type_bytes(&self) -> &'a [u8; 6] { &self.header.id_type } - pub fn version(&self) -> u32 { self.header.version } - pub fn issue_date_timestamp(&self) -> i64 { self.header.issue_date_timestamp } - pub fn next_update_timestamp(&self) -> i64 { self.header.next_update_timestamp } + pub fn id_type_bytes(&self) -> &'a [u8; 6] { + &self.header.id_type + } + pub fn version(&self) -> u32 { + self.header.version + } + pub fn issue_date_timestamp(&self) -> i64 { + self.header.issue_date_timestamp + } + pub fn next_update_timestamp(&self) -> i64 { + self.header.next_update_timestamp + } // Parses the hex string and returns the byte array - pub fn fmspc(&self) -> [u8; 6] { - hex::decode(from_utf8(&self.header.fmspc_hex).unwrap()).unwrap().try_into().unwrap() + pub fn fmspc(&self) -> [u8; 6] { + hex::decode(from_utf8(&self.header.fmspc_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } // Parses the hex string and returns the byte array - pub fn pce_id(&self) -> [u8; 2] { - hex::decode(from_utf8(&self.header.pce_id_hex).unwrap()).unwrap().try_into().unwrap() - } - pub fn tcb_type(&self) -> u8 { self.header.tcb_type } - pub fn tcb_evaluation_data_number(&self) -> u32 { self.header.tcb_evaluation_data_number } + pub fn pce_id(&self) -> [u8; 2] { + hex::decode(from_utf8(&self.header.pce_id_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() + } + pub fn tcb_type(&self) -> u8 { + self.header.tcb_type + } + pub fn tcb_evaluation_data_number(&self) -> u32 { + self.header.tcb_evaluation_data_number + } // --- Parsed/Structured Accessors --- pub fn tdx_module(&self) -> Option> { - self.tdx_module_data_payload.map(TdxModulePodDataZeroCopy::new) + self.tdx_module_data_payload + .map(TdxModulePodDataZeroCopy::new) } - pub fn tdx_module_identities_count(&self) -> u32 { self.header.tdx_module_identities_count } + pub fn tdx_module_identities_count(&self) -> u32 { + self.header.tdx_module_identities_count + } pub fn tdx_module_identities(&self) -> TdxModuleIdentityIter<'a> { TdxModuleIdentityIter::new( self.tdx_module_identities_section_payload, @@ -99,7 +123,9 @@ impl<'a> TcbInfoZeroCopy<'a> { ) } - pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels_count(&self) -> u32 { + self.header.tcb_levels_count + } pub fn tcb_levels(&self) -> TcbLevelIter<'a> { TcbLevelIter::new( self.tcb_levels_section_payload, @@ -116,15 +142,26 @@ pub struct TdxModulePodDataZeroCopy<'a> { pub data: &'a TdxModulePodData, } impl<'a> TdxModulePodDataZeroCopy<'a> { - pub fn new(data: &'a TdxModulePodData) -> Self { Self { data } } + pub fn new(data: &'a TdxModulePodData) -> Self { + Self { data } + } pub fn mrsigner(&self) -> [u8; 48] { - hex::decode(from_utf8(&self.data.mrsigner_hex).unwrap()).unwrap().try_into().unwrap() + hex::decode(from_utf8(&self.data.mrsigner_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } - pub fn attributes(&self) -> [u8; 8] { - hex::decode(from_utf8(&self.data.attributes_hex).unwrap()).unwrap().try_into().unwrap() + pub fn attributes(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.data.attributes_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } - pub fn attributes_mask(&self) -> [u8; 8] { - hex::decode(from_utf8(&self.data.attributes_mask_hex).unwrap()).unwrap().try_into().unwrap() + pub fn attributes_mask(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.data.attributes_mask_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } } @@ -141,16 +178,22 @@ impl<'a> TcbComponentZeroCopy<'a> { pub fn new(header: &'a TcbComponentHeader, payload: &'a [u8]) -> Result { let cat_len = header.category_len as usize; let comp_type_len = header.component_type_len as usize; - if cat_len.checked_add(comp_type_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + if cat_len + .checked_add(comp_type_len) + .ok_or(ZeroCopyError::InvalidOffset)? + > payload.len() + { return Err(ZeroCopyError::InvalidSliceLength); } Ok(Self { header, category_payload: &payload[..cat_len], - component_type_payload: &payload[cat_len .. cat_len + comp_type_len], + component_type_payload: &payload[cat_len..cat_len + comp_type_len], }) } - pub fn cpusvn(&self) -> u8 { self.header.cpusvn } + pub fn cpusvn(&self) -> u8 { + self.header.cpusvn + } pub fn category_str(&self) -> Result<&'a str, ZeroCopyError> { from_utf8(self.category_payload).map_err(|_| ZeroCopyError::InvalidUtf8) } @@ -172,21 +215,31 @@ impl<'a> TdxTcbLevelZeroCopy<'a> { pub fn new(header: &'a TdxTcbLevelHeader, payload: &'a [u8]) -> Result { let lengths_len = header.advisory_ids_lengths_array_len as usize; let data_len = header.advisory_ids_data_total_len as usize; - if lengths_len.checked_add(data_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + if lengths_len + .checked_add(data_len) + .ok_or(ZeroCopyError::InvalidOffset)? + > payload.len() + { return Err(ZeroCopyError::InvalidSliceLength); } Ok(Self { header, advisory_ids_lengths_payload: &payload[..lengths_len], - advisory_ids_data_payload: &payload[lengths_len .. lengths_len + data_len], + advisory_ids_data_payload: &payload[lengths_len..lengths_len + data_len], }) } - pub fn tcb_isvsvn(&self) -> u8 { self.header.tcb_isvsvn } - pub fn tcb_status(&self) -> u8 { + pub fn tcb_isvsvn(&self) -> u8 { + self.header.tcb_isvsvn + } + pub fn tcb_status(&self) -> u8 { self.header.tcb_status } - pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } - pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + pub fn tcb_date_timestamp(&self) -> i64 { + self.header.tcb_date_timestamp + } + pub fn advisory_ids_count(&self) -> u32 { + self.header.advisory_ids_count + } pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { AdvisoryIdIter::new( self.advisory_ids_lengths_payload, @@ -200,45 +253,66 @@ impl<'a> TdxTcbLevelZeroCopy<'a> { #[derive(Debug, Copy, Clone)] pub struct TdxModuleIdentityZeroCopy<'a> { pub header: &'a TdxModuleIdentityHeader, - id_payload: &'a [u8], // Slice for the ID string + id_payload: &'a [u8], // Slice for the ID string tcb_levels_section_payload: &'a [u8], // Slice for all TdxTcbLevels of this identity } impl<'a> TdxModuleIdentityZeroCopy<'a> { // 'payload' is specific to this TdxModuleIdentity (id_string + its TdxTcbLevels section) - pub fn new(header: &'a TdxModuleIdentityHeader, payload: &'a [u8]) -> Result { + pub fn new( + header: &'a TdxModuleIdentityHeader, + payload: &'a [u8], + ) -> Result { let id_len = header.id_len as usize; let tcb_levels_len = header.tcb_levels_total_payload_len as usize; let tcb_levels_alignment = core::mem::align_of::(); - let offset = id_len + (tcb_levels_alignment - 1) & !(tcb_levels_alignment - 1); + let offset = (id_len + (tcb_levels_alignment - 1)) & !(tcb_levels_alignment - 1); - if offset.checked_add(tcb_levels_len).ok_or(ZeroCopyError::InvalidOffset)? > payload.len() { + if offset + .checked_add(tcb_levels_len) + .ok_or(ZeroCopyError::InvalidOffset)? + > payload.len() + { return Err(ZeroCopyError::InvalidSliceLength); } Ok(Self { header, id_payload: &payload[..id_len], - tcb_levels_section_payload: &payload[offset .. offset + tcb_levels_len], + tcb_levels_section_payload: &payload[offset..offset + tcb_levels_len], }) } pub fn mrsigner(&self) -> [u8; 48] { - hex::decode(from_utf8(&self.header.mrsigner_hex).unwrap()).unwrap().try_into().unwrap() + hex::decode(from_utf8(&self.header.mrsigner_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } - pub fn attributes(&self) -> [u8; 8] { - hex::decode(from_utf8(&self.header.attributes_hex).unwrap()).unwrap().try_into().unwrap() + pub fn attributes(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.header.attributes_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } - pub fn attributes_mask(&self) -> [u8; 8] { - hex::decode(from_utf8(&self.header.attributes_mask_hex).unwrap()).unwrap().try_into().unwrap() + pub fn attributes_mask(&self) -> [u8; 8] { + hex::decode(from_utf8(&self.header.attributes_mask_hex).unwrap()) + .unwrap() + .try_into() + .unwrap() } pub fn id_str(&self) -> Result<&'a str, ZeroCopyError> { from_utf8(self.id_payload).map_err(|_| ZeroCopyError::InvalidUtf8) } - pub fn tcb_levels_count(&self) -> u32 { self.header.tcb_levels_count } + pub fn tcb_levels_count(&self) -> u32 { + self.header.tcb_levels_count + } pub fn tcb_levels(&self) -> TdxTcbLevelIter<'a> { - TdxTcbLevelIter::new(self.tcb_levels_section_payload, self.header.tcb_levels_count) + TdxTcbLevelIter::new( + self.tcb_levels_section_payload, + self.header.tcb_levels_count, + ) } } @@ -256,33 +330,39 @@ impl<'a> TcbLevelZeroCopy<'a> { // 'payload' is specific to this TcbLevel (sgx_comp_strings | tdx_comp_strings (opt) | adv_id_lengths | adv_id_data) pub fn new(header: &'a TcbLevelHeader, payload: &'a [u8]) -> Result { let mut current_offset = 0; - + let sgx_len = header.sgx_components_strings_total_len as usize; - let sgx_payload = payload.get(current_offset..current_offset + sgx_len) + let sgx_payload = payload + .get(current_offset..current_offset + sgx_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += sgx_len; let tdx_payload = if header.tdx_tcb_components_present == 1 { let tdx_len = header.tdx_components_strings_total_len as usize; - let slice = payload.get(current_offset..current_offset + tdx_len) + let slice = payload + .get(current_offset..current_offset + tdx_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += tdx_len; Some(slice) } else { None }; - + let adv_lengths_len = header.advisory_ids_lengths_array_len as usize; - let adv_lengths_payload = payload.get(current_offset..current_offset + adv_lengths_len) + let adv_lengths_payload = payload + .get(current_offset..current_offset + adv_lengths_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += adv_lengths_len; let adv_data_len = header.advisory_ids_data_total_len as usize; - let adv_data_payload = payload.get(current_offset..current_offset + adv_data_len) + let adv_data_payload = payload + .get(current_offset..current_offset + adv_data_len) .ok_or(ZeroCopyError::InvalidSliceLength)?; current_offset += adv_data_len; - if current_offset > payload.len() { return Err(ZeroCopyError::InvalidSliceLength); } + if current_offset > payload.len() { + return Err(ZeroCopyError::InvalidSliceLength); + } Ok(Self { header, @@ -295,8 +375,12 @@ impl<'a> TcbLevelZeroCopy<'a> { pub fn tcb_status(&self) -> u8 { self.header.tcb_status } - pub fn pce_svn(&self) -> u16 { self.header.pce_svn } - pub fn tcb_date_timestamp(&self) -> i64 { self.header.tcb_date_timestamp } + pub fn pce_svn(&self) -> u16 { + self.header.pce_svn + } + pub fn tcb_date_timestamp(&self) -> i64 { + self.header.tcb_date_timestamp + } pub fn sgx_tcb_components(&self) -> TcbComponentIter<'a> { TcbComponentIter::new( &self.header.sgx_tcb_components, @@ -305,14 +389,15 @@ impl<'a> TcbLevelZeroCopy<'a> { } pub fn tdx_tcb_components(&self) -> Option> { if self.header.tdx_tcb_components_present == 1 { - self.tdx_components_strings_payload.map(|payload| { - TcbComponentIter::new(&self.header.tdx_tcb_components, payload) - }) + self.tdx_components_strings_payload + .map(|payload| TcbComponentIter::new(&self.header.tdx_tcb_components, payload)) } else { None } } - pub fn advisory_ids_count(&self) -> u32 { self.header.advisory_ids_count } + pub fn advisory_ids_count(&self) -> u32 { + self.header.advisory_ids_count + } pub fn advisory_ids(&self) -> AdvisoryIdIter<'a> { AdvisoryIdIter::new( self.advisory_ids_lengths_payload, diff --git a/src/types/quote/cert_data.rs b/src/types/quote/cert_data.rs index 46ce8b1..71bb136 100644 --- a/src/types/quote/cert_data.rs +++ b/src/types/quote/cert_data.rs @@ -74,7 +74,9 @@ impl<'a> QuoteCertData<'a> { let first_cert = cert_chain_processor::load_first_cert_from_pem_data(self.cert_data) .context("Failed to parse PCK certificate chain")?; - let pck_extension = first_cert.tbs_certificate.extensions + let pck_extension = first_cert + .tbs_certificate + .extensions .as_ref() .and_then(|extensions| { extensions diff --git a/src/types/quote/header.rs b/src/types/quote/header.rs index 03a271f..b8e9de9 100644 --- a/src/types/quote/header.rs +++ b/src/types/quote/header.rs @@ -64,7 +64,10 @@ impl TryFrom<[u8; std::mem::size_of::()]> for QuoteHeader { ::read_from(&value).expect("failed to read quote header"); if quote_header.version.get() < 3 || quote_header.version.get() > 5 { - return Err(anyhow!("unsupported quote version: {}", quote_header.version)); + return Err(anyhow!( + "unsupported quote version: {}", + quote_header.version + )); } if quote_header.attestation_key_type.get() != AttestationKeyType::Ecdsa256P256 as u16 { diff --git a/src/types/report.rs b/src/types/report.rs index a743af8..01dad4d 100644 --- a/src/types/report.rs +++ b/src/types/report.rs @@ -175,8 +175,7 @@ pub struct Td15ReportBody { pub tee_tcb_svn2: [u8; 16], /// (600) Measurement of the initial contents of the Migration TD - pub mr_service_td: [u8; 48] - // Total 648 bytes + pub mr_service_td: [u8; 48], // Total 648 bytes } impl TryFrom<[u8; std::mem::size_of::()]> for Td15ReportBody { @@ -188,4 +187,4 @@ impl TryFrom<[u8; std::mem::size_of::()]> for Td15ReportBody { Ok(report) } -} \ No newline at end of file +} diff --git a/src/types/tcb_info.rs b/src/types/tcb_info.rs index 2e1ac8e..c6ad2bf 100644 --- a/src/types/tcb_info.rs +++ b/src/types/tcb_info.rs @@ -1,6 +1,6 @@ use std::{str::from_utf8, time::SystemTime}; -use anyhow::{Result, Context, bail}; +use anyhow::{Context, Result, bail}; use chrono::{DateTime, Utc}; use p256::ecdsa::VerifyingKey; use p256::ecdsa::signature::Verifier; diff --git a/src/utils.rs b/src/utils.rs index 12eac1d..e643b26 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -162,9 +162,8 @@ pub mod cert_chain_processor { let cert_slice = &pem_data[start..end]; // Try PEM format first - match CertificateInner::from_pem(cert_slice) { - Ok(cert) => return Ok(cert), - Err(_) => {}, // Try DER next + if let Ok(cert) = CertificateInner::from_pem(cert_slice) { + return Ok(cert); } // Try DER format as fallback (if this was base64 decoded already) @@ -199,7 +198,6 @@ pub mod cert_chain_processor { } parse_single_cert(pem_data, ranges[0]) } - } pub trait Expireable { @@ -262,4 +260,4 @@ pub fn read_bytes<'a>(bytes: &mut &'a [u8], size: usize) -> &'a [u8] { let (front, rest) = bytes.split_at(size); *bytes = rest; front -} \ No newline at end of file +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 9f02710..80fdc21 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -4,9 +4,9 @@ use x509_cert::{crl::CertificateList, der::Decode}; use dcap_rs::types::tcb_info::TcbInfoAndSignature; use dcap_rs::{ - types::enclave_identity::QuotingEnclaveIdentityAndSignature, - utils::cert_chain_processor, + types::enclave_identity::QuotingEnclaveIdentityAndSignature, types::{collateral::Collateral, quote::Quote}, + utils::cert_chain_processor, }; pub fn sgx_quote_data() -> (Collateral, Quote<'static>) { diff --git a/tests/tests.rs b/tests/tests.rs index 0e4c560..0d4b372 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ mod common; -use dcap_rs::{verify_dcap_quote, types::quote::Quote}; use common::*; +use dcap_rs::{types::quote::Quote, verify_dcap_quote}; #[test] fn parse_tdx_v5_quote() { From 270554e1c5d7237196de132a4d1b889374a96d74 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Fri, 4 Jul 2025 16:01:00 +0800 Subject: [PATCH 10/14] fixed code commenting --- src/types/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index 06731ae..c82e549 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -22,7 +22,7 @@ const TD15_REPORT_LEN: usize = 648; // TD15_REPORT // serialization: // [quote_vesion][quote_body_type][tcb_status][fmspc][quote_body_raw_bytes][abi-encoded string array of tcb_advisory_ids] // 2 bytes + 2 bytes + 1 byte + 6 bytes + var (SGX_ENCLAVE_REPORT = 384; TD10_REPORT = 584; TD15_REPORT = 648) + var -// total: 13 + (384 or 584) + var bytes +// total: 11 + (384 or 584 or 648) + var bytes #[derive(Debug)] pub struct VerifiedOutput { pub quote_version: u16, From 8d14db4503da9f9a9a65c121311b60af9652d695 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Mon, 7 Jul 2025 17:04:46 +0800 Subject: [PATCH 11/14] tdx module conditional compile --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index f449114..6c388fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "full")] pub mod tdx; pub mod trust_store; pub mod types; From ee2b89dfc1e8c917693a3b226a8a9578d4d272dc Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Mon, 7 Jul 2025 18:05:20 +0800 Subject: [PATCH 12/14] collateral conditional compiling --- src/types/collateral.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/collateral.rs b/src/types/collateral.rs index cb32bed..f178a0a 100644 --- a/src/types/collateral.rs +++ b/src/types/collateral.rs @@ -4,7 +4,6 @@ use crate::utils::keccak; use crate::utils::{cert_chain, crl}; use anyhow::Result; use serde::{Deserialize, Serialize}; -#[cfg(not(feature = "zero-copy"))] use x509_cert::certificate::CertificateInner; use x509_cert::{ crl::CertificateList, From d78f3798af5cc1da58326e205015ee918e4abe12 Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Tue, 2 Sep 2025 16:12:54 +0800 Subject: [PATCH 13/14] collateral object abi-encoding/decoding implementation --- data/abi/encoded.bin | Bin 0 -> 7808 bytes src/lib.rs | 5 ++ src/types/collateral.rs | 103 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 data/abi/encoded.bin diff --git a/data/abi/encoded.bin b/data/abi/encoded.bin new file mode 100644 index 0000000000000000000000000000000000000000..3d362c58abedf7dd0d6fbbca136d71bb3f449d91 GIT binary patch literal 7808 zcmeHLc~}$I7Ka2`1Xr*w6o{dqwgm6&Gw8EISc?masKsY>l1v~92_^xHTR{b_xKqIm zi^~&H+^T3>M6`%St5&O`QmwnyJ{1M6wN`z10!jinY5V!w_s1KOkKA+5@9g*7d&kB` z@W~gPwGjzV+qhW4cN{A$ptJC_jjb=3ZYu@pl_FbP(AwVd?ac6F|F9E_Ks?e7bg>u5 z*oj>_L}-m%f+RX@q-3a0XOzeTU(B_BF(=pQlXQC4sL^Q=H_(yCIk-4Svsxo7kteWv z7I6knJk`-f1mO(>dsk6RbmulG4looT48mX_77hsj2@S*i-arQJ632MBL93vRUi@!w zyDs33G&d>dQWcwre_S_X=aQjiQSM9+`yz8E#1S?iRMg$($FaH^+4@--`Q-34QKhOj~5e#5BKTecqQ%S8pavIcY=!m->u!vJ zb36UEy3eeu*iB#0s&fkqeK%W2Jsq1Fu2}h{!69p4MDNIc1%aC#k6K5~dHhzFK_0PB zFBrDh}&^y;?A5g)j;R!}x5yTcLh$+n(~; zR!fO=&$If+JA^@eFdiSM8NOpv`00t49lyNuplr5nklPo+D5%Q8jYKTqqwoH_kX?A?&1k2d@| zSs287^(0HSW#8h%)pH8>46eQZRU2`rdaiZU(eS9Ep`}MO>(}2KS-EFK$?@V}RMt_; z2A)*wh9*Br8?)qss5I&u-_)~KDwj`|$W>$GBQ>5Ae{knvh*9zD@tOg`svwB1Ei+tQ zG01LXkF&O=hVSHEi_6lif|$j7Gu(3)`ah?atV-~I?ruM1U9nYAyJ~s9bNo-KyTAHj zOObO#*2T}ZF0=|#e(JhBs{hk(?=QN!?uq-o%sXS53Bn*gRZH@s-(4JW-g5{Me6$jj zb=`fx&Q=%&BQfdf#PGt$9UmO*Q?SXQBKw$!M~8H)AlKQEnC~)r|B{WJ+Z|4>?wm|t zv{LH4eHkYY(e4uqE)DFka#d`V@zc8Jn`Q~C;!)YNr>qO~oV2*liD%!Xujmy~Yg@ci z7zMq0Yw3B#jBKZzr&+ZDRMB4FwR=yE5C-w8=BV!%M1*I?#jh-17*H`l{muk`gLTx_ z{bc*nlL@<*j&83?-;j1P->cfeI;!SUq#t&i+;}Kqy`ju=dDyOj|17bNnsKgZURJTw ziG!6+n5nGQ zyEuahALQlX=xr6`IqS3X9kO}e*E$@l3(Wl@_Fm=Yhr%E}?8~3}-)bF3M6HfHm$S%? z`Z`MHOPvpeuZBrVqUc{h*wpR@#%=A{`YQ7rS{CPt%j>?Nm?vs8kY2lUv<&)b3LL&BjzYG&bK?Cu!l1Uybho^^K?y}46(QfU@n)r)W zL4JJ?K3kmneWy20A7g4aEIsQ>v0 zy{_DFFXXBWVC*>SsMS-3=>7L!D(|u2X6e15tyb`ZBE*Z2~$hvIviiKBctfM}?7Xyaw{OKzX3QiqiuRUS?anH|$zYd|Q6H zovrP2J9vAu-v?ayu`53o0UPtO5Qo`-V3EWts7{0d(k*J&*5341qrP?>fjunYCKvh_FB`6xU3cp6xOdRUlJn#o;D7ame?{g;KTB_` zav!CQ-QQON+x?|HpQ1@FJFefnvEcTYwWWaxVZqMlqLg_dZO`vrH)7nHrbQ|+wu_%E zH7XSmTD49ZFj<)6DY*z0B=ci9gf32v9n~-dJ z9Rv+v2PG#eIK4Cgp-htlxD+nIAPpF0GMzWu5ceSz09#BFKnyAM=9|&b7HC)tG`s~G zVWMGv<~m3d4Y8n6CK|NfOfTI6mTAt1fEH-=eptvu>iw{!)%#&dtM|i_R_});t=JzWDHI=4p!G^V>HAX;3)*ANzX4}T7?iSN>CJuD;U6%G^-$0N(87`1g8)} zMIboFp(>VCC@54(GYC+!3Ie`XRjU=8LWPh(MRI@z6h$crT#3^RAk=C~NnsepkSu)P zsz7NJqgVu0aDh1B2Y1YCf;AAcYb z|79oeKkftqdk%EMVnb~vs0Yf&2+4;G(8qj}tGIk2d{k zvh}hEL2v}zL{U&EDNg8g=7)|4>n b zO`{}=sTFEWO)&};ASjkYQ5B&=I0dc3R18o8B}3Dgg2n*{_jHnEaf*REFF|o?ing-# F{s(myvVH&n literal 0 HcmV?d00001 diff --git a/src/lib.rs b/src/lib.rs index 6c388fd..698c23b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,8 @@ +// Re-exports + +// Re-export alloy_sol_types for abi-encoding +pub use alloy_sol_types; + #[cfg(feature = "full")] pub mod tdx; pub mod trust_store; diff --git a/src/types/collateral.rs b/src/types/collateral.rs index f178a0a..8b71da3 100644 --- a/src/types/collateral.rs +++ b/src/types/collateral.rs @@ -2,6 +2,7 @@ use crate::utils::cert_chain_processor; use crate::utils::keccak; use crate::utils::{cert_chain, crl}; +use alloy_sol_types::{SolType, sol}; use anyhow::Result; use serde::{Deserialize, Serialize}; use x509_cert::certificate::CertificateInner; @@ -12,6 +13,8 @@ use x509_cert::{ use super::{enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature}; +pub type CollateralSol = sol!((bytes, bytes, bytes[2], string, string)); + #[derive(Debug, Serialize, Deserialize)] pub struct Collateral { /* Certificate Revocation List */ @@ -86,6 +89,60 @@ impl Collateral { let tbs = crl.tbs_cert_list.to_der()?; Ok(keccak::hash(&tbs)) } + + /// Encode the Collateral struct to Solidity ABI format + pub fn sol_abi_encode(&self) -> Result> { + // Convert CRLs to DER-encoding raw bytes for ABI encoding + let root_ca_crl_bytes = self.root_ca_crl.to_der()?; + let pck_crl_bytes = self.pck_crl.to_der()?; + + // Encode certificate chain as ABI-encoded DER bytes array (of fixed size == 2) + let tcb_issuer_chain = &self.tcb_info_and_qe_identity_issuer_chain; + let mut chain_bytes: [Vec; 2] = [vec![], vec![]]; + for (i, cert) in tcb_issuer_chain.iter().enumerate() { + let cert_der = cert.to_der()?; + chain_bytes[i] = cert_der; + } + + // Serialize structured data to JSON strings + let tcb_info_json = serde_json::to_string(&self.tcb_info)?; + let qe_identity_json = serde_json::to_string(&self.qe_identity)?; + + // Create tuple for ABI encoding: (bytes, bytes, bytes[2], string, string) + let encoded = CollateralSol::abi_encode_params(&( + root_ca_crl_bytes, + pck_crl_bytes, + chain_bytes, + tcb_info_json, + qe_identity_json, + )); + Ok(encoded) + } + + /// Decode Solidity ABI encoded bytes back to Collateral struct + pub fn sol_abi_decode(encoded: &[u8]) -> Result { + use pem::{Pem, encode}; + + // Decode the ABI encoded tuple: (bytes, bytes, bytes, string, string) + let (root_ca_crl_bytes, pck_crl_bytes, chain_bytes, tcb_info_json, qe_identity_json) = + CollateralSol::abi_decode_params(encoded)?; + + let mut pem_chain = String::new(); + let tcb_pem = Pem::new(String::from("CERTIFICATE"), chain_bytes[0].to_vec().clone()); + + let root_pem = Pem::new(String::from("CERTIFICATE"), chain_bytes[1].to_vec().clone()); + + pem_chain.push_str(&encode(&tcb_pem)); + pem_chain.push_str(&encode(&root_pem)); + + Ok(Self::new( + &root_ca_crl_bytes, + &pck_crl_bytes, + pem_chain.as_bytes(), + &tcb_info_json, + &qe_identity_json, + )?) + } } #[cfg(test)] @@ -113,4 +170,50 @@ mod tests { let json = include_str!("../../data/full_collateral_sgx.json"); let _collateral: Collateral = serde_json::from_str(json).expect("json to parse"); } + + #[test] + fn test_abi_encode_collateral() { + let collateral = Collateral::new( + include_bytes!("../../data/intel_root_ca_crl.der"), + include_bytes!("../../data/pck_platform_crl.der"), + include_bytes!("../../data/tcb_signing_cert.pem"), + include_str!("../../data/tcb_info_v2.json"), + include_str!("../../data/qeidentityv2.json"), + ) + .expect("collateral to be created"); + + let encoded = collateral.sol_abi_encode().expect("collateral to abi encode"); + assert!(!encoded.is_empty(), "ABI encoded data should not be empty"); + + // Write encoded data to file for test_abi_decode_collateral to use + std::fs::create_dir_all("data/abi/").expect("failed to create directory"); + std::fs::write("data/abi/encoded.bin", &encoded).expect("failed to write encoded data"); + + println!("ABI encoded length: {} bytes", encoded.len()); + } + + #[test] + fn test_abi_decode_collateral() { + // Read the encoded data written by test_abi_encode_collateral + let encoded = std::fs::read("data/abi/encoded.bin").expect("failed to read encoded data"); + let decoded = Collateral::sol_abi_decode(&encoded).expect("collateral to abi decode"); + + // Create original collateral for comparison + let original = Collateral::new( + include_bytes!("../../data/intel_root_ca_crl.der"), + include_bytes!("../../data/pck_platform_crl.der"), + include_bytes!("../../data/tcb_signing_cert.pem"), + include_str!("../../data/tcb_info_v2.json"), + include_str!("../../data/qeidentityv2.json"), + ) + .expect("collateral to be created"); + + // Verify the decoded data matches original + let original_json = serde_json::to_string(&original).expect("original to serialize"); + let decoded_json = serde_json::to_string(&decoded).expect("decoded to serialize"); + assert_eq!( + original_json, decoded_json, + "Decoded collateral should match original" + ); + } } From ba9d0beb2e3a30847526f8e3b2f6a929bdbc900f Mon Sep 17 00:00:00 2001 From: Preston Ong Date: Tue, 2 Sep 2025 16:25:20 +0800 Subject: [PATCH 14/14] do not re-export alloy-sol-types --- src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 698c23b..6c388fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,3 @@ -// Re-exports - -// Re-export alloy_sol_types for abi-encoding -pub use alloy_sol_types; - #[cfg(feature = "full")] pub mod tdx; pub mod trust_store;