diff --git a/.github/workflows/CI-ci-remote.yml b/.github/workflows/CI-ci-remote.yml new file mode 100644 index 0000000..7f77459 --- /dev/null +++ b/.github/workflows/CI-ci-remote.yml @@ -0,0 +1,31 @@ +name: CI-build-test-lint-fmt-deps + +run-name: "Workflow performing CI steps: build, testing, check format, check linting, check headers and check dependencies" + +on: + pull_request: + types: [opened, synchronize] + push: + branches: + - main + schedule: + - cron: "0 6 * * SAT" + workflow_dispatch: + +env: + RUST_BACKTRACE: 1 + +jobs: + build-test-check: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + - name: Install latest stable + uses: dtolnay/rust-toolchain@stable + - name: Install latest nightly + uses: dtolnay/rust-toolchain@nightly # udeps + - name: Install cargo-make + run: cargo install --debug cargo-make + - name: Run CI full + run: cargo make ci-remote diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8dbaae3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +[package] +version = "0.1.0" +name = "tee-verifier" +authors = ["Horizen Labs "] +repository = "https://github.com/zkVerify/tee-verifier" +homepage = "https://horizenlabs.io" +keywords = ["cryptography", "elliptic-curves", "pairing", "zk-SNARKs"] +categories = ["cryptography"] +include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] +license = "MIT/Apache-2.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.42", default-features = false } +asn1_der = { version = "0.7.6", default-features = false, features = ["native_types"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +hex-literal = { version = "0.4.1", default-features = false } +sha2 = { version = "0.10.9", default-features = false } +serde = { version = "1.0.228", default-features = false, features = ["derive", "alloc"] } +serde-json-core = { version = "0.6.0", default-features = false, features = ["heapless"] } +p256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } +pem = { version = "3.0.6", default-features = false } +spki = { version = "0.7.3", default-features = false } +x509-verify = { version = "0.4.8", default-features = false, features = [ + "x509", + "p256", + "k256", + "ed25519", + "p384", + "p521", + "sha2", +] } + +[dev-dependencies] +rstest = { version = "0.19.0", default-features = false } +assert_ok = { version = "1.0.2" } + +[features] +default = ["std"] +std = [] diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..b2d6925 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,91 @@ +[config] +default_to_workspace = false + +[tasks.ignore-members] +workspace = false + +[tasks.clean] +command = "cargo" +args = ["clean"] + +[tasks.install-bare-metal] +command = "rustup" +args = ["target", "add", "thumbv7em-none-eabi"] + +[tasks.build-bare-metal] +dependencies = ["install-bare-metal"] +command = "cargo" +args = ["build", "--no-default-features", "--target", "thumbv7em-none-eabi"] + +[tasks.build] +command = "cargo" +args = ["build"] + +[tasks.test] +command = "cargo" +args = ["test", "--release"] + +[tasks.format-inst] +install_crate = { crate_name = "rustfmt", rustup_component_name = "rustfmt", binary = "rustfmt", test_arg = "--help" } + +[tasks.format] +dependencies = ["format-inst"] +command = "cargo" +args = ["fmt"] + +[tasks.format-check] +dependencies = ["format-inst"] +command = "cargo" +args = ["fmt", "--check"] + +[tasks.clippy-inst] +install_crate = { crate_name = "clippy", rustup_component_name = "clippy", binary = "clippy", test_arg = "--help" } + +[tasks.clippy] +dependencies = ["clippy-inst"] +command = "cargo" +args = ["clippy", "--", "--deny", "warnings"] + +[tasks.audit-inst] +command = "cargo" +args = ["install", "cargo-audit"] + +[tasks.audit] +dependencies = ["audit-inst"] +command = "cargo" +args = ["audit"] + +[tasks.cov] +command = "cargo" +args = ["llvm-cov", "--workspace", "--lcov", "--output-path", "lcov.info"] + +[tasks.udeps-inst] +toolchain = "nightly" +command = "cargo" +args = ["install", "cargo-udeps", "--locked"] + +[tasks.udeps] +dependencies = ["udeps-inst"] +toolchain = "nightly" +command = "cargo" +args = ["udeps", "--all-targets"] + +[tasks.machete] +command = "cargo" +args = ["machete"] + +[tasks.ci-common] +dependencies = [ + "build", + "build-bare-metal", + "test", + "clippy", + "audit", + "udeps" +] + +[tasks.ci] +dependencies = ["format", "ci-common", "machete"] + +[tasks.ci-remote] +dependencies = ["format-check", "ci-common"] diff --git a/assets/Intel_SGX_Provisioning_Certification_RootCA.cer b/assets/Intel_SGX_Provisioning_Certification_RootCA.cer new file mode 100644 index 0000000..768806c Binary files /dev/null and b/assets/Intel_SGX_Provisioning_Certification_RootCA.cer differ diff --git a/assets/tests/intel/crl.pem b/assets/tests/intel/crl.pem new file mode 100644 index 0000000..6b4bad0 --- /dev/null +++ b/assets/tests/intel/crl.pem @@ -0,0 +1,9 @@ +-----BEGIN X509 CRL----- +MIIBKjCB0QIBATAKBggqhkjOPQQDAjBxMSMwIQYDVQQDDBpJbnRlbCBTR1ggUENL +IFByb2Nlc3NvciBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNV +BAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMXDTI2MDIw +MzA5MzI1M1oXDTI2MDMwNTA5MzI1M1qgLzAtMAoGA1UdFAQDAgEBMB8GA1UdIwQY +MBaAFNDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMCA0gAMEUCIHdBXcia +9o0XoEzUXyt6sOLWsBf1sxf+LWHkw8f3nrgZAiEAiOpFb1mpxYOhAU2ytWbRc5HH +pV39Rn4Zgw1/9kW8HCI= +-----END X509 CRL----- diff --git a/assets/tests/intel/crl_chain.pem b/assets/tests/intel/crl_chain.pem new file mode 100644 index 0000000..cfc2f98 --- /dev/null +++ b/assets/tests/intel/crl_chain.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIICmDCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC +MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD +b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw +CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHExIzAh +BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl +bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB +MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg +tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i +HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww +UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl +cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFNDo +qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG +AQH/AgEAMAoGCCqGSM49BAMCA0gAMEUCIQCJgTbtVqOyZ1m3jqiAXM6QYa6r5sWS +4y/G7y8uIJGxdwIgRqPvBSKzzQagBLQq5s5A70pdoiaRJ8z/0uDz4NgV91k= +-----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/assets/tests/intel/crl_chain_platform.pem b/assets/tests/intel/crl_chain_platform.pem new file mode 100644 index 0000000..44feaf5 --- /dev/null +++ b/assets/tests/intel/crl_chain_platform.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIICljCCAj2gAwIBAgIVAJVvXc29G+HpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC +MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD +b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw +CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg +BgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs +IENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex +CzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB/7t21lXSO +2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z+UiRZCnqR7psOvgqFeSxlmTlJl +eTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS +BgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy +dmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d +zb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB +Af8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w+i6VYGW3UF/22uaXe0YJDj1Ue +nA+TjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN+ +-----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/assets/tests/intel/crl_chain_platform_ko.pem b/assets/tests/intel/crl_chain_platform_ko.pem new file mode 100644 index 0000000..aa743af --- /dev/null +++ b/assets/tests/intel/crl_chain_platform_ko.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIICljCCAj2gAwIBAgIVAJVvXc29G+HpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC +MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD +b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw +CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg +BgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs +IENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex +CzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB/7t21lXSO +2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z+UiRZCnqR7psOvgqFeSxlmTlJl +eTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp01ifODtJVSv1AbOScGrDBS +BgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy +dmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d +zb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYB +Af8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w+i6VYGW3UF/22uaXe0YJDj1Ue +nA+TjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN+ +-----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/assets/tests/intel/crl_platform.pem b/assets/tests/intel/crl_platform.pem new file mode 100644 index 0000000..800d184 --- /dev/null +++ b/assets/tests/intel/crl_platform.pem @@ -0,0 +1,66 @@ +-----BEGIN X509 CRL----- +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 +Tp1XhGcxUZZ0+Yqf +-----END X509 CRL----- diff --git a/assets/tests/intel/quote_90.dat b/assets/tests/intel/quote_90.dat new file mode 100644 index 0000000..c41c14b Binary files /dev/null and b/assets/tests/intel/quote_90.dat differ diff --git a/assets/tests/intel/quote_b0.dat b/assets/tests/intel/quote_b0.dat new file mode 100644 index 0000000..667f510 Binary files /dev/null and b/assets/tests/intel/quote_b0.dat differ diff --git a/assets/tests/intel/quote_no_cert.dat b/assets/tests/intel/quote_no_cert.dat new file mode 100644 index 0000000..ea8b4ee Binary files /dev/null and b/assets/tests/intel/quote_no_cert.dat differ diff --git a/assets/tests/intel/tcb_info_90.json b/assets/tests/intel/tcb_info_90.json new file mode 100644 index 0000000..5afa32b --- /dev/null +++ b/assets/tests/intel/tcb_info_90.json @@ -0,0 +1 @@ +{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2026-01-20T10:27:17Z","nextUpdate":"2026-02-19T10:27:17Z","fmspc":"90C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":18,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":3},"tcbDate":"2024-11-13T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":6},"tcbDate":"2024-11-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01099"]},{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01099"]}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":3,"category":"BIOS","type":"Early Microcode Update"},{"svn":3,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":4,"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":3,"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-11-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":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":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01079","INTEL-SA-01099","INTEL-SA-01103","INTEL-SA-01111"]},{"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","INTEL-SA-01036","INTEL-SA-01079","INTEL-SA-01099","INTEL-SA-01103","INTEL-SA-01111"]}]},"signature":"a6d213371f53a1127229c40e3a3aec31f70eb862aa715a07b6ea009732d71ca4b2db30b2ed5ff50765890b6cdb50fd499ad11b74f44206f627fc216be95ea522"} \ No newline at end of file diff --git a/assets/tests/intel/tcb_info_90.pem b/assets/tests/intel/tcb_info_90.pem new file mode 100644 index 0000000..c359a81 --- /dev/null +++ b/assets/tests/intel/tcb_info_90.pem @@ -0,0 +1,34 @@ +-----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----- + + diff --git a/assets/tests/intel/tcb_info_b0.json b/assets/tests/intel/tcb_info_b0.json new file mode 100644 index 0000000..536af12 --- /dev/null +++ b/assets/tests/intel/tcb_info_b0.json @@ -0,0 +1 @@ +{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2026-01-20T08:55:31Z","nextUpdate":"2026-02-19T08:55:31Z","fmspc":"B0C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":18,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":3},"tcbDate":"2024-11-13T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":6},"tcbDate":"2024-11-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01099"]},{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01099"]}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":3,"category":"BIOS","type":"Early Microcode Update"},{"svn":3,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":4,"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":11,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":3,"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-11-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":11,"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":"OutOfDate","advisoryIDs":["INTEL-SA-01036","INTEL-SA-01079","INTEL-SA-01099","INTEL-SA-01103","INTEL-SA-01111"]},{"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","INTEL-SA-01036","INTEL-SA-01079","INTEL-SA-01099","INTEL-SA-01103","INTEL-SA-01111"]}]},"signature":"57929dbac3872b5389c604b5eec1a3edfb2bfe5c06b0b5b00778ed7396490789b8886c289ea2ef94c5ed0d509345042a0316f8466e32d3ba62233a0455bf500f"} \ No newline at end of file diff --git a/assets/tests/intel/tcb_info_b0.pem b/assets/tests/intel/tcb_info_b0.pem new file mode 100644 index 0000000..c359a81 --- /dev/null +++ b/assets/tests/intel/tcb_info_b0.pem @@ -0,0 +1,34 @@ +-----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----- + + diff --git a/src/cert.rs b/src/cert.rs new file mode 100644 index 0000000..080e347 --- /dev/null +++ b/src/cert.rs @@ -0,0 +1,439 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; +use alloc::vec::Vec; + +use asn1_der::typed::{DerDecodable, Sequence}; +use p256::ecdsa::signature::Verifier; +use spki::ObjectIdentifier; +use x509_verify::{ + x509_cert::{ + crl::CertificateList, + der::{Decode, Encode}, + Certificate, + }, + Signature, VerifyInfo, VerifyingKey, +}; + +/// Errors that can occur during certificate operations. +#[derive(Debug)] +pub enum CertificateError { + /// Failed to parse certificate data. + Parse, + /// Public key verification failed. + KeyVerification, + /// The certificate chain is empty. + EmptyChain, + /// The certificate has no extensions. + NoExtensions, + /// A required extension was not found. + ExtensionNotFound, + /// The signature is invalid. + BadSignature, + /// The certificate has been revoked. + RevokedCertificate, + /// The certificate is not yet valid. + CertificateNotYetValid, + /// The certificate has expired. + CertificateExpired, +} + +/// Identifies a revoked certificate by its issuer and serial number. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RevokedCertId { + /// DER-encoded issuer name. + pub issuer: Vec, + /// Serial number bytes. + pub serial_number: Vec, +} + +/// A certificate revocation list as a list of revoked certificate identifiers. +pub type Crl = Vec; + +fn verify_crl(crl: &CertificateList, key: &VerifyingKey) -> Result<(), CertificateError> { + let verify_info = VerifyInfo::new( + crl.tbs_cert_list + .to_der() + .map_err(|_| CertificateError::Parse)? + .into(), + Signature::new( + &crl.signature_algorithm, + crl.signature + .as_bytes() + .ok_or(CertificateError::BadSignature)?, + ), + ); + key.verify(&verify_info) + .map_err(|_| CertificateError::KeyVerification) +} + +pub fn verify_pem_cert_chain( + pck_certificate_chain_pem: &Vec, + root_cert: Option<&[u8]>, + crl: Option<&Crl>, + now: u64, +) -> Result { + let pems = pem::parse_many(pck_certificate_chain_pem).map_err(|_| CertificateError::Parse)?; + let certs: Result, _> = pems + .into_iter() + .map(|pem| Certificate::from_der(pem.contents())) + .collect(); + let mut certs = certs.map_err(|_| CertificateError::Parse)?; + + if certs.is_empty() { + return Err(CertificateError::EmptyChain); + } + + if let Some(r) = root_cert { + let root = Certificate::from_der(r).map_err(|_| CertificateError::Parse)?; + certs.push(root); + } + for c in 0..certs.len() - 1 { + let key: VerifyingKey = certs[c + 1] + .tbs_certificate + .subject_public_key_info + .clone() + .try_into() + .map_err(|_| CertificateError::KeyVerification)?; + verify_certificate(&certs[c], &key, crl, now)?; + } + + Ok(certs[0].clone()) +} + +fn verify_certificate( + cert: &Certificate, + key: &VerifyingKey, + crl: Option<&Crl>, + now: u64, +) -> Result<(), CertificateError> { + // Check certificate validity period + let not_before = cert + .tbs_certificate + .validity + .not_before + .to_unix_duration() + .as_secs(); + let not_after = cert + .tbs_certificate + .validity + .not_after + .to_unix_duration() + .as_secs(); + + if now < not_before { + return Err(CertificateError::CertificateNotYetValid); + } + if now > not_after { + return Err(CertificateError::CertificateExpired); + } + + if let Some(c) = crl { + let issuer = cert + .tbs_certificate + .issuer + .to_der() + .map_err(|_| CertificateError::Parse)?; + let serial = cert.tbs_certificate.serial_number.as_bytes().to_vec(); + let cert_id = RevokedCertId { + issuer, + serial_number: serial, + }; + if c.contains(&cert_id) { + return Err(CertificateError::RevokedCertificate); + } + } + + let verify_info = VerifyInfo::new( + cert.tbs_certificate + .to_der() + .map_err(|_| CertificateError::Parse)? + .into(), + Signature::new( + &cert.signature_algorithm, + cert.signature + .as_bytes() + .ok_or(CertificateError::BadSignature)?, + ), + ); + key.verify(&verify_info) + .map_err(|_| CertificateError::KeyVerification) +} + +pub fn get_ext(cert: &Certificate, oid: ObjectIdentifier) -> Result<&[u8], CertificateError> { + if cert.tbs_certificate.extensions.is_none() { + return Err(CertificateError::NoExtensions); + } + if let Some(ext) = &cert.tbs_certificate.extensions { + for e in ext { + if e.extn_id == oid { + return Ok(e.extn_value.as_bytes()); + } + } + } + Err(CertificateError::ExtensionNotFound) +} + +pub fn extract_field(data: &[u8], oid: ObjectIdentifier) -> Result<&[u8], CertificateError> { + let seq = Sequence::decode(data).map_err(|_| CertificateError::ExtensionNotFound)?; + + for i in 0..seq.len() { + let Ok(elem) = seq.get(i) else { continue }; + let Ok(item) = Sequence::load(elem) else { + continue; + }; + if item.len() < 2 { + continue; + } + let Ok(oid_obj) = item.get(0) else { continue }; + if oid_obj.value() == oid.as_bytes() { + let Ok(val_obj) = item.get(1) else { continue }; + return Ok(val_obj.value()); + } + } + Err(CertificateError::ExtensionNotFound) +} + +/// Parse an ASN.1 sequence containing an OID-value pair +/// Returns (value bytes, total sequence length in bytes) +pub fn parse_oid_value_pair<'a>( + data: &'a [u8], + oid: &ObjectIdentifier, +) -> Result<(&'a [u8], usize), CertificateError> { + // Calculate the DER-encoded length from the header + // Tag (1 byte) + Length field (variable) + Content + if data.len() < 2 { + return Err(CertificateError::ExtensionNotFound); + } + + let (content_len, header_len) = if data[1] < 128 { + // Short form: length byte directly encodes the length + (data[1] as usize, 2) + } else { + // Long form: data[1] & 0x7F is the number of length bytes + let num_len_bytes = (data[1] & 0x7F) as usize; + if data.len() < 2 + num_len_bytes { + return Err(CertificateError::ExtensionNotFound); + } + let mut len: usize = 0; + for i in 0..num_len_bytes { + len = (len << 8) | (data[2 + i] as usize); + } + (len, 2 + num_len_bytes) + }; + + let seq_len = header_len + content_len; + + // Now decode just this sequence + let seq = + Sequence::decode(&data[..seq_len]).map_err(|_| CertificateError::ExtensionNotFound)?; + + if seq.len() != 2 { + return Err(CertificateError::ExtensionNotFound); + } + + let name = seq + .get(0) + .map_err(|_| CertificateError::ExtensionNotFound)? + .value(); + if name[..name.len() - 1] != *oid.as_bytes() { + return Err(CertificateError::ExtensionNotFound); + } + + let val_obj = seq + .get(1) + .map_err(|_| CertificateError::ExtensionNotFound)?; + + Ok((val_obj.value(), seq_len)) +} + +pub fn verify_signature( + cert: &Certificate, + data: &[u8], + signature: &[u8], +) -> Result<(), CertificateError> { + let point = p256::EncodedPoint::from_bytes( + cert.tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .ok_or(CertificateError::BadSignature)?, + ) + .map_err(|_| CertificateError::BadSignature)?; + let pck_verifying_key = p256::ecdsa::VerifyingKey::from_encoded_point(&point) + .map_err(|_| CertificateError::BadSignature)?; + + pck_verifying_key + .verify( + data, + &p256::ecdsa::Signature::from_bytes(signature.into()) + .map_err(|_| CertificateError::BadSignature)?, + ) + .map_err(|_| CertificateError::BadSignature)?; + Ok(()) +} + +// PUBLIC INTERFACE +pub fn parse_crl( + crl_pem: &Vec, + pck_certificate_chain_pem: &Vec, + root_cert: Option<&[u8]>, + now: u64, +) -> Result<(u64, Crl), CertificateError> { + let pems = pem::parse_many(crl_pem).map_err(|_| CertificateError::Parse)?; + let crls: Result, _> = pems + .into_iter() + .map(|pem| CertificateList::from_der(pem.contents())) + .collect(); + let crls = crls.map_err(|_| CertificateError::Parse)?; + + let sign_cert = verify_pem_cert_chain(pck_certificate_chain_pem, root_cert, None, now)?; + let sign_key: VerifyingKey = sign_cert + .tbs_certificate + .subject_public_key_info + .clone() + .try_into() + .map_err(|_| CertificateError::KeyVerification)?; + + let mut revoked_certs: Crl = Vec::new(); + let mut latest_this_update: u64 = 0; + + for crl in &crls { + verify_crl(crl, &sign_key)?; + + // Extract this_update and track the latest one + let this_update_duration = crl.tbs_cert_list.this_update.to_unix_duration().as_secs(); + + latest_this_update = match this_update_duration { + current if current > this_update_duration => current, + _ => this_update_duration, + }; + + let issuer = crl + .tbs_cert_list + .issuer + .to_der() + .map_err(|_| CertificateError::Parse)?; + + if let Some(revoked) = &crl.tbs_cert_list.revoked_certificates { + for entry in revoked { + revoked_certs.push(RevokedCertId { + issuer: issuer.clone(), + serial_number: entry.serial_number.as_bytes().to_vec(), + }); + } + } + } + + Ok((latest_this_update, revoked_certs)) +} + +#[cfg(test)] +mod should { + use crate::cert::{parse_crl, CertificateError}; + use chrono::DateTime; + use rstest::rstest; + use std::{fs::File, io::Read}; + + fn load_file(path: &str) -> Vec { + let mut f = File::open(path).unwrap(); + let mut buf = Vec::new(); + f.read_to_end(&mut buf).unwrap(); + buf + } + + fn load_root_cert() -> Vec { + load_file("assets/Intel_SGX_Provisioning_Certification_RootCA.cer") + } + + #[rstest] + #[case( + "assets/tests/intel/crl.pem", + "assets/tests/intel/crl_chain.pem", + "2026-02-03T09:32:53Z" + )] + #[case( + "assets/tests/intel/crl_platform.pem", + "assets/tests/intel/crl_chain_platform.pem", + "2026-02-03T10:55:02Z" + )] + #[should_panic(expected = "KeyVerification")] + #[case( + "assets/tests/intel/crl_platform.pem", + "assets/tests/intel/crl_chain_platform_ko.pem", + "2026-02-03T10:55:02Z" + )] + fn parse_quote(#[case] crl_path: &str, #[case] crl_chain_path: &str, #[case] exp_date: &str) { + let crl_buf = load_file(crl_path); + let crl_chain_buf = load_file(crl_chain_path); + let root_buf = load_root_cert(); + + let now = DateTime::parse_from_rfc3339(exp_date) + .unwrap() + .timestamp() + .try_into() + .unwrap(); + let (date, _crl) = parse_crl(&crl_buf, &crl_chain_buf, Some(&root_buf), now).unwrap(); + assert_eq!(date, now); + } + + #[rstest] + #[case( + "assets/tests/intel/crl.pem", + "assets/tests/intel/crl_chain.pem", + // Before certificate notBefore (May 21 2018) + "2018-01-01T00:00:00Z" + )] + fn reject_certificate_not_yet_valid( + #[case] crl_path: &str, + #[case] crl_chain_path: &str, + #[case] timestamp: &str, + ) { + let crl_buf = load_file(crl_path); + let crl_chain_buf = load_file(crl_chain_path); + let root_buf = load_root_cert(); + + let now = DateTime::parse_from_rfc3339(timestamp).unwrap().timestamp() as u64; + + let result = parse_crl(&crl_buf, &crl_chain_buf, Some(&root_buf), now); + assert!(matches!( + result, + Err(CertificateError::CertificateNotYetValid) + )); + } + + #[rstest] + #[case( + "assets/tests/intel/crl.pem", + "assets/tests/intel/crl_chain.pem", + // After certificate notAfter (May 21 2033) + "2034-01-01T00:00:00Z" + )] + fn reject_certificate_expired( + #[case] crl_path: &str, + #[case] crl_chain_path: &str, + #[case] timestamp: &str, + ) { + let crl_buf = load_file(crl_path); + let crl_chain_buf = load_file(crl_chain_path); + let root_buf = load_root_cert(); + + let now = DateTime::parse_from_rfc3339(timestamp).unwrap().timestamp() as u64; + + let result = parse_crl(&crl_buf, &crl_chain_buf, Some(&root_buf), now); + assert!(matches!(result, Err(CertificateError::CertificateExpired))); + } +} diff --git a/src/intel.rs b/src/intel.rs new file mode 100644 index 0000000..3a1cbee --- /dev/null +++ b/src/intel.rs @@ -0,0 +1,33 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +static ROOT_CERT: &[u8] = + include_bytes!("../assets/Intel_SGX_Provisioning_Certification_RootCA.cer"); + +mod collaterals; +mod constants; +mod quote; + +pub use collaterals::{CollateralError, TcbResponse}; +pub use quote::{ParseError, QuoteV4, VerificationError}; + +/// Parse a TCB response from JSON bytes. +pub fn parse_tcb_response(input: &[u8]) -> Result { + collaterals::parse_tcb_response(input) +} + +pub fn parse_quote(input: &[u8]) -> Result { + quote::parse_quote(input) +} diff --git a/src/intel/collaterals.rs b/src/intel/collaterals.rs new file mode 100644 index 0000000..5b737a6 --- /dev/null +++ b/src/intel/collaterals.rs @@ -0,0 +1,343 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; +use alloc::string::String; +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; + +use crate::intel::constants::{ECDSA_SIGNATURE_SIZE, MAX_COLLATERAL_SIZE}; +use crate::intel::quote::{PceSvn, TcbSvn}; + +/// Errors that can occur during collateral verification. +#[derive(Debug)] +pub enum CollateralError { + /// Failed to parse collateral data. + Parse, + /// The TCB info is invalid. + InvalidTcb, + /// The collateral is not yet valid. + TooEarly, + /// The collateral has expired. + Expired, + /// PCK certificate chain verification failed. + PKCChain, + /// The signature is invalid. + BadSignature, +} + +/// A signed TCB info response from Intel. +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TcbResponse { + /// The TCB info payload. + pub tcb_info: TcbInfo, + signature: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TcbInfo { + pub id: String, + pub version: u8, + pub issue_date: String, + pub next_update: String, + pub fmspc: String, + pub pce_id: String, + pub tcb_type: u32, + pub tcb_evaluation_data_number: u32, + pub tdx_module: TdxModule, + pub tdx_module_identities: Vec, + pub tcb_levels: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TdxModule { + pub mrsigner: String, + pub attributes: String, + pub attributes_mask: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TdxModuleIdentity { + pub id: String, + pub mrsigner: String, + pub attributes: String, + pub attributes_mask: String, + pub tcb_levels: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TcbLevel { + pub tcb: Tcb, + pub tcb_date: String, + pub tcb_status: TcbStatus, + #[serde(rename = "advisoryIDs")] + #[serde(skip_serializing_if = "Option::is_none")] + pub advisory_ids: Option>, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Tcb { + #[serde(rename = "isvsvn")] + #[serde(skip_serializing_if = "Option::is_none")] + pub isv_svn: Option, // from the docs, "integer" + #[serde(rename = "sgxtcbcomponents")] + #[serde(skip_serializing_if = "Option::is_none")] + pub sgx_components: Option>, + #[serde(rename = "pcesvn")] + #[serde(skip_serializing_if = "Option::is_none")] + pub pce_svn: Option, + #[serde(rename = "tdxtcbcomponents", default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub tdx_components: Option>, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct TcbComponents { + pub svn: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub ttype: Option, +} + +impl PartialEq for TcbComponents { + fn eq(&self, other: &u8) -> bool { + self.svn == *other + } +} + +impl PartialOrd for TcbComponents { + fn partial_cmp(&self, other: &u8) -> Option { + Some(self.svn.cmp(other)) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd)] +pub enum TcbStatus { + UpToDate = 0, + OutOfDateConfigurationNeeded = 1, + OutOfDate = 2, + ConfigurationAndSWHardeningNeeded = 3, + ConfigurationNeeded = 4, + SWHardeningNeeded = 5, + Revoked = 6, +} + +impl TcbResponse { + /// Verify the TCB response signature and validity period. + pub fn verify( + &self, + certs: Vec, + crl: &crate::cert::Crl, + now: u64, + ) -> Result<(), CollateralError> { + let cert = crate::cert::verify_pem_cert_chain( + &certs, + Some(crate::intel::ROOT_CERT), + Some(crl), + now, + ) + .map_err(|_| CollateralError::PKCChain)?; + let data: serde_json_core::heapless::Vec = + serde_json_core::to_vec(&self.tcb_info).map_err(|_| CollateralError::PKCChain)?; + let mut signature_bytes = [0u8; ECDSA_SIGNATURE_SIZE]; + hex::decode_to_slice(&self.signature, &mut signature_bytes) + .map_err(|_| CollateralError::PKCChain)?; + crate::cert::verify_signature(&cert, data.as_slice(), &signature_bytes) + .map_err(|_| CollateralError::BadSignature)?; + self.tcb_info.verify(now) + } +} + +impl TcbInfo { + pub fn verify(&self, now: u64) -> Result<(), CollateralError> { + let issue = chrono::DateTime::parse_from_rfc3339(&self.issue_date) + .map_err(|_| CollateralError::InvalidTcb)? + .timestamp() as u64; + if now < issue { + return Err(CollateralError::TooEarly); + } + let next = chrono::DateTime::parse_from_rfc3339(&self.next_update) + .map_err(|_| CollateralError::InvalidTcb)? + .timestamp() as u64; + if now > next { + return Err(CollateralError::Expired); + } + + Ok(()) + } +} + +/// Parse a TCB response from JSON bytes. +pub fn parse_tcb_response(input: &[u8]) -> Result { + let (tcb_response, _): (TcbResponse, usize) = + serde_json_core::from_slice(input).map_err(|_| CollateralError::Parse)?; + Ok(tcb_response) +} + +pub fn compare_tcb_levels(quote_tcb: &TcbSvn, levels: &Vec) -> (TcbStatus, PceSvn) { + // Compare SVNs in TEE TCB SVN array retrieved from TD Report in Quote (from index 0 to 15 if TEE TCB SVN at index 1 is set to 0, or from index 2 to 15 otherwise) with the corresponding values of SVNs in tdxtcbcomponents array of TCB Level. If all TEE TCB SVNs in the TD Report are greater or equal to the corresponding values in TCB Level, read tcbStatus assigned to this TCB level. Otherwise, move to the next item on TCB Levels list. + for tcb_level in levels { + if tcb_level.tcb.tdx_components.is_none() { + continue; + } + let coll_tcb = &tcb_level + .tcb + .tdx_components + .as_ref() + .expect("tdx_components checked for Some above"); + if quote_tcb.len() != coll_tcb.len() { + continue; + } + let mut t = match quote_tcb[1] { + 0 => 0, + _ => 2, // as per documentation + }; + while t < quote_tcb.len() { + if coll_tcb[t] > quote_tcb[t] { + break; + } + t += 1; + } + if t < quote_tcb.len() { + continue; + } + return (tcb_level.tcb_status.clone(), 0); + } + (TcbStatus::Revoked, 0) +} + +/// Compare SGX TCB SVNs extracted from the PCK certificate against the +/// sgxtcbcomponents in each TCB level of the collateral. +pub fn compare_sgx_tcb_levels(cert_tcb: &TcbSvn, levels: &Vec) -> (TcbStatus, PceSvn) { + for tcb_level in levels { + let Some(coll_tcb) = tcb_level.tcb.sgx_components.as_ref() else { + continue; + }; + if cert_tcb.len() != coll_tcb.len() { + continue; + } + let mut t = 0; + while t < cert_tcb.len() { + if coll_tcb[t] > cert_tcb[t] { + break; + } + t += 1; + } + if t < cert_tcb.len() { + continue; + } + let pce = tcb_level.tcb.pce_svn.unwrap_or(0); + return (tcb_level.tcb_status.clone(), pce); + } + (TcbStatus::Revoked, 0) +} + +#[cfg(test)] +mod should { + use super::parse_tcb_response; + use assert_ok::assert_ok; + use std::{fs::File, io::Read}; + + fn load_file(path: &str) -> Vec { + let mut f = File::open(path).unwrap(); + let mut buf = Vec::new(); + f.read_to_end(&mut buf).unwrap(); + buf + } + + #[test] + fn parse_tcb_info() { + let buf = load_file("assets/tests/intel/tcb_info_90.json"); + assert_ok!(parse_tcb_response(&buf)); + } + + #[test] + fn verify_tcb_response() { + let tcb_buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&tcb_buf).unwrap(); + + let chain_buf = load_file("assets/tests/intel/tcb_info_90.pem"); + + let time = chrono::DateTime::parse_from_rfc3339("2026-01-23T16:43:44Z") + .unwrap() + .timestamp() as u64; + let crl: crate::cert::Crl = vec![]; + assert_ok!(tcb.verify(chain_buf, &crl, time)); + } + + #[test] + fn verify_at_exact_issue_date() { + let buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&buf).unwrap(); + + let issue_date = chrono::DateTime::parse_from_rfc3339(&tcb.tcb_info.issue_date) + .unwrap() + .timestamp() as u64; + + assert_ok!(tcb.tcb_info.verify(issue_date)); + } + + #[test] + fn verify_at_exact_next_update() { + let buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&buf).unwrap(); + + let next_update = chrono::DateTime::parse_from_rfc3339(&tcb.tcb_info.next_update) + .unwrap() + .timestamp() as u64; + + assert_ok!(tcb.tcb_info.verify(next_update)); + } + + #[test] + fn reject_one_second_before_issue_date() { + let buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&buf).unwrap(); + + let issue_date = chrono::DateTime::parse_from_rfc3339(&tcb.tcb_info.issue_date) + .unwrap() + .timestamp() as u64; + + assert!(matches!( + tcb.tcb_info.verify(issue_date - 1), + Err(super::CollateralError::TooEarly) + )); + } + + #[test] + fn reject_one_second_after_next_update() { + let buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&buf).unwrap(); + + let next_update = chrono::DateTime::parse_from_rfc3339(&tcb.tcb_info.next_update) + .unwrap() + .timestamp() as u64; + + assert!(matches!( + tcb.tcb_info.verify(next_update + 1), + Err(super::CollateralError::Expired) + )); + } +} diff --git a/src/intel/constants.rs b/src/intel/constants.rs new file mode 100644 index 0000000..15c951a --- /dev/null +++ b/src/intel/constants.rs @@ -0,0 +1,149 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Constants for Intel SGX/TDX quote parsing and verification. + +// ============================================================================= +// Top-level structure sizes +// ============================================================================= + +pub const MAX_COLLATERAL_SIZE: usize = 8192; +pub const QE_REPORT_SIZE: usize = 384; +pub const ECDSA_SIGNATURE_SIZE: usize = 64; +pub const ATTESTATION_KEY_SIZE: usize = 64; +pub const QUOTE_HEADER_SIZE: usize = 48; +pub const QUOTE_BODY_SIZE: usize = 584; + +// ============================================================================= +// Certification data types +// ============================================================================= + +pub const CERT_DATA_TYPE_PCK_CHAIN: u16 = 5; +pub const CERT_DATA_TYPE_QE_REPORT: u16 = 6; + +// ============================================================================= +// Intel OIDs +// ============================================================================= + +pub const INTEL_SGX_OID: &str = "1.2.840.113741.1.13.1"; +pub const INTEL_TCB_OID: &str = "1.2.840.113741.1.13.1.2"; +pub const INTEL_FMSPC_OID: &str = "1.2.840.113741.1.13.1.4"; + +// ============================================================================= +// Intel vendor ID +// ============================================================================= + +pub const INTEL_VENDOR_ID: [u8; 16] = hex_literal::hex!("939A7233F79C4CA9940A0DB3957F0607"); + +// ============================================================================= +// QuoteHeader field sizes +// ============================================================================= + +pub const HEADER_VERSION_SIZE: usize = 2; +pub const HEADER_ATTESTATION_KEY_TYPE_SIZE: usize = 2; +pub const HEADER_TEE_TYPE_SIZE: usize = 4; +pub const HEADER_RESERVED1_SIZE: usize = 2; +pub const HEADER_RESERVED2_SIZE: usize = 2; +pub const HEADER_QE_VENDOR_ID_SIZE: usize = 16; +pub const HEADER_USER_DATA_SIZE: usize = 20; + +// QuoteHeader field offsets (derived from sizes) +pub const HEADER_VERSION_OFFSET: usize = 0; +pub const HEADER_ATTESTATION_KEY_TYPE_OFFSET: usize = HEADER_VERSION_OFFSET + HEADER_VERSION_SIZE; +pub const HEADER_TEE_TYPE_OFFSET: usize = + HEADER_ATTESTATION_KEY_TYPE_OFFSET + HEADER_ATTESTATION_KEY_TYPE_SIZE; +pub const HEADER_RESERVED1_OFFSET: usize = HEADER_TEE_TYPE_OFFSET + HEADER_TEE_TYPE_SIZE; +pub const HEADER_RESERVED2_OFFSET: usize = HEADER_RESERVED1_OFFSET + HEADER_RESERVED1_SIZE; +pub const HEADER_QE_VENDOR_ID_OFFSET: usize = HEADER_RESERVED2_OFFSET + HEADER_RESERVED2_SIZE; +pub const HEADER_USER_DATA_OFFSET: usize = HEADER_QE_VENDOR_ID_OFFSET + HEADER_QE_VENDOR_ID_SIZE; + +// ============================================================================= +// QuoteBodyV4 field sizes +// ============================================================================= + +pub const BODY_TEE_TCB_SVN_SIZE: usize = 16; +pub const BODY_MRSEAM_SIZE: usize = 48; +pub const BODY_MRSIGNERSEAM_SIZE: usize = 48; +pub const BODY_SEAMATTRIBUTES_SIZE: usize = 8; +pub const BODY_TDATTRIBUTES_SIZE: usize = 8; +pub const BODY_XFAM_SIZE: usize = 8; +pub const BODY_MRTD_SIZE: usize = 48; +pub const BODY_MRCONFIGID_SIZE: usize = 48; +pub const BODY_MROWNER_SIZE: usize = 48; +pub const BODY_MROWNERCONFIG_SIZE: usize = 48; +pub const BODY_RTMR_SIZE: usize = 48; +pub const BODY_REPORTDATA_SIZE: usize = 64; + +// QuoteBodyV4 field offsets (derived from sizes) +pub const BODY_TEE_TCB_SVN_OFFSET: usize = 0; +pub const BODY_MRSEAM_OFFSET: usize = BODY_TEE_TCB_SVN_OFFSET + BODY_TEE_TCB_SVN_SIZE; +pub const BODY_MRSIGNERSEAM_OFFSET: usize = BODY_MRSEAM_OFFSET + BODY_MRSEAM_SIZE; +pub const BODY_SEAMATTRIBUTES_OFFSET: usize = BODY_MRSIGNERSEAM_OFFSET + BODY_MRSIGNERSEAM_SIZE; +pub const BODY_TDATTRIBUTES_OFFSET: usize = BODY_SEAMATTRIBUTES_OFFSET + BODY_SEAMATTRIBUTES_SIZE; +pub const BODY_XFAM_OFFSET: usize = BODY_TDATTRIBUTES_OFFSET + BODY_TDATTRIBUTES_SIZE; +pub const BODY_MRTD_OFFSET: usize = BODY_XFAM_OFFSET + BODY_XFAM_SIZE; +pub const BODY_MRCONFIGID_OFFSET: usize = BODY_MRTD_OFFSET + BODY_MRTD_SIZE; +pub const BODY_MROWNER_OFFSET: usize = BODY_MRCONFIGID_OFFSET + BODY_MRCONFIGID_SIZE; +pub const BODY_MROWNERCONFIG_OFFSET: usize = BODY_MROWNER_OFFSET + BODY_MROWNER_SIZE; +pub const BODY_RTMR0_OFFSET: usize = BODY_MROWNERCONFIG_OFFSET + BODY_MROWNERCONFIG_SIZE; +pub const BODY_RTMR1_OFFSET: usize = BODY_RTMR0_OFFSET + BODY_RTMR_SIZE; +pub const BODY_RTMR2_OFFSET: usize = BODY_RTMR1_OFFSET + BODY_RTMR_SIZE; +pub const BODY_RTMR3_OFFSET: usize = BODY_RTMR2_OFFSET + BODY_RTMR_SIZE; +pub const BODY_REPORTDATA_OFFSET: usize = BODY_RTMR3_OFFSET + BODY_RTMR_SIZE; + +// ============================================================================= +// QeAuthenticationData / QeCertificationData field sizes +// ============================================================================= + +pub const AUTH_DATA_SIZE_FIELD: usize = 2; +pub const CERT_DATA_TYPE_FIELD_SIZE: usize = 2; +pub const CERT_DATA_SIZE_FIELD: usize = 4; +pub const CERT_DATA_HEADER_SIZE: usize = CERT_DATA_TYPE_FIELD_SIZE + CERT_DATA_SIZE_FIELD; + +// ============================================================================= +// QeReportCertificationData constants +// ============================================================================= + +pub const REPORT_DATA_SIZE: usize = 64; +pub const HASH_PADDING_SIZE: usize = 32; + +// ============================================================================= +// QuoteV4 constants +// ============================================================================= + +pub const SIGNATURE_DATA_LEN_SIZE: usize = 4; + +// ============================================================================= +// Certificate / TCB extraction constants +// ============================================================================= + +pub const TCB_SVN_COUNT: usize = 16; +pub const FMSPC_SIZE: usize = 6; + +// ============================================================================= +// Attestation key types +// ============================================================================= + +/// ECDSA-256-with-P-256 curve attestation key type +pub const ATTESTATION_KEY_TYPE_ECDSA_256_P256: i16 = 2; + +// ============================================================================= +// TD Attributes +// ============================================================================= + +/// Index of the debug flag byte in TD attributes +pub const TDATTRIBUTES_DEBUG_INDEX: usize = 0; +/// Expected value when debug mode is disabled +pub const TDATTRIBUTES_DEBUG_DISABLED: u8 = 0; diff --git a/src/intel/quote.rs b/src/intel/quote.rs new file mode 100644 index 0000000..3a3e04f --- /dev/null +++ b/src/intel/quote.rs @@ -0,0 +1,750 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; +use alloc::vec::Vec; + +use p256::ecdsa::{signature::Verifier, Signature, VerifyingKey}; +use sha2::{Digest, Sha256}; +use spki::ObjectIdentifier; + +use crate::intel::collaterals::{TcbInfo, TcbLevel, TcbStatus}; +use crate::intel::constants::*; + +/// Errors that can occur when parsing a quote. +#[derive(Debug, PartialEq)] +pub enum ParseError { + /// The quote header is invalid or too short. + InvalidHeader, + /// The QE authentication data is invalid. + InvalidAuthenticationData, + /// The QE report is invalid. + InvalidQeReport, + /// The QE report signature is invalid. + InvalidQeReportSignature, + /// The quote body is invalid or too short. + InvalidBody, + /// The certification data is invalid. + InvalidCertificationData, + /// The signature data is invalid. + InvalidSignatureData, + /// The attestation key type is not supported. + UnsupportedAttestationKeyType, + /// The certification data type is not supported. + UnsupportedCertificationDataType, + /// The QE vendor ID is not recognized. + UnsupportedVendorId, +} + +/// Errors that can occur when verifying a quote. +#[derive(Debug, PartialEq)] +pub enum VerificationError { + /// Quote verification failed. + FailedVerification, + /// The verification type is not supported. + UnsupportedVerificationType, + /// P-256 elliptic curve error. + P256Error, + /// PCK certificate chain verification failed. + PKCChain, + /// Failed to extract Intel SGX extensions from the certificate. + CannotExtractIntelExtensions, + /// Failed to extract the FMSPC value. + CannotExtractFmspc, + /// The FMSPC value does not match the expected value. + FmspcMismatch, + /// The TD is running in debug mode. + DebugModeEnabled, + /// The TCB status is not acceptable. + BadTcbStatus(TcbStatus), + /// The PCE SVN status is not acceptable. + BadPceStatus, + /// The signature is invalid. + BadSignature, + /// The QE report data is invalid. + InvalidQeReportData, +} + +#[derive(Debug)] +struct QuoteHeader { + /// Version of the quote_no_cert.data structure. + version: i16, + /// Type of the Attestation Key used by the Quoting Enclave. + /// Supported values: + /// 2 (ECDSA-256-with-P-256 curve) + /// 3 (ECDSA-384-with-P-384 curve) (Note: currently not supported) + /// (Note: 0 and 1 are reserved, for when EPID is moved to version 4 quotes.) + attestation_key_type: i16, + /// TEE for this Attestation + /// 0x00000000: SGX + /// 0x00000081: TDX + tee_type: i32, + /// reserved + reserved1: [u8; HEADER_RESERVED1_SIZE], + /// reserved + reserved2: [u8; HEADER_RESERVED2_SIZE], + /// Unique identifier of the QE Vendor. + /// Value: 939A7233F79C4CA9940A0DB3957F0607 (Intel® SGX QE Vendor) + qe_vendor_id: [u8; HEADER_QE_VENDOR_ID_SIZE], + /// 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). This + /// identifier is consistent for every quote generated + /// with this QE on this platform. + user_data: [u8; HEADER_USER_DATA_SIZE], +} + +impl QuoteHeader { + fn from_bytes(input: &[u8]) -> Result { + if input.len() < QUOTE_HEADER_SIZE { + return Err(ParseError::InvalidHeader); + } + Ok(QuoteHeader { + version: i16::from_le_bytes( + input[HEADER_VERSION_OFFSET..HEADER_ATTESTATION_KEY_TYPE_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + ), + attestation_key_type: i16::from_le_bytes( + input[HEADER_ATTESTATION_KEY_TYPE_OFFSET..HEADER_TEE_TYPE_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + ), + tee_type: i32::from_le_bytes( + input[HEADER_TEE_TYPE_OFFSET..HEADER_RESERVED1_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + ), + reserved1: input[HEADER_RESERVED1_OFFSET..HEADER_RESERVED2_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + reserved2: input[HEADER_RESERVED2_OFFSET..HEADER_QE_VENDOR_ID_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + qe_vendor_id: input[HEADER_QE_VENDOR_ID_OFFSET..HEADER_USER_DATA_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + user_data: input[HEADER_USER_DATA_OFFSET..QUOTE_HEADER_SIZE] + .try_into() + .map_err(|_| ParseError::InvalidHeader)?, + }) + } + + fn to_bytes(&self, output: &mut [u8]) { + output[HEADER_VERSION_OFFSET..HEADER_ATTESTATION_KEY_TYPE_OFFSET] + .copy_from_slice(&self.version.to_le_bytes()); + output[HEADER_ATTESTATION_KEY_TYPE_OFFSET..HEADER_TEE_TYPE_OFFSET] + .copy_from_slice(&self.attestation_key_type.to_le_bytes()); + output[HEADER_TEE_TYPE_OFFSET..HEADER_RESERVED1_OFFSET] + .copy_from_slice(&self.tee_type.to_le_bytes()); + output[HEADER_RESERVED1_OFFSET..HEADER_RESERVED2_OFFSET].copy_from_slice(&self.reserved1); + output[HEADER_RESERVED2_OFFSET..HEADER_QE_VENDOR_ID_OFFSET] + .copy_from_slice(&self.reserved2); + output[HEADER_QE_VENDOR_ID_OFFSET..HEADER_USER_DATA_OFFSET] + .copy_from_slice(&self.qe_vendor_id); + output[HEADER_USER_DATA_OFFSET..QUOTE_HEADER_SIZE].copy_from_slice(&self.user_data); + } +} + +#[derive(Debug)] +struct QeAuthenticationData { + _size: i16, + data: Vec, +} + +impl QeAuthenticationData { + fn from_bytes(input: &[u8]) -> Result { + let size = i16::from_le_bytes( + input[..AUTH_DATA_SIZE_FIELD] + .try_into() + .map_err(|_| ParseError::InvalidAuthenticationData)?, + ); + let data = input[AUTH_DATA_SIZE_FIELD..AUTH_DATA_SIZE_FIELD + size as usize].to_vec(); + Ok(QeAuthenticationData { _size: size, data }) + } +} + +#[derive(Debug)] +struct QeReportCertificationData { + /// GX Report of the Quoting Enclave that generated an Attestation Key. + /// Report Data: SHA256(ECDSA Attestation Key || QE Authentication Data) || 32-0x00’s + qe_report: [u8; QE_REPORT_SIZE], // expands to EnclaveReportBody + /// ECDSA signature over the QE Report calculated using the Provisioning Certification Key (PCK). + qe_report_signature: [u8; ECDSA_SIGNATURE_SIZE], //Signature, + /// Variable-length data chosen by the Quoting Enclave and signed by the + /// Provisioning Certification Key (as a part of the Report Data in the QE Report) + qe_authentication_data: QeAuthenticationData, + qe_certification_data: QeCertificationData, +} + +impl QeReportCertificationData { + fn from_bytes(input: &[u8]) -> Result { + let qe_report = &input[..QE_REPORT_SIZE]; + let qe_report_signature = &input[QE_REPORT_SIZE..QE_REPORT_SIZE + ECDSA_SIGNATURE_SIZE]; + let qe_authentication_data = + QeAuthenticationData::from_bytes(&input[QE_REPORT_SIZE + ECDSA_SIGNATURE_SIZE..])?; + let qe_certification_data = QeCertificationData::from_bytes( + &input[QE_REPORT_SIZE + + ECDSA_SIGNATURE_SIZE + + AUTH_DATA_SIZE_FIELD + + qe_authentication_data.data.len()..], + )?; + Ok(QeReportCertificationData { + qe_report: qe_report + .try_into() + .map_err(|_| ParseError::InvalidQeReport)?, + qe_report_signature: qe_report_signature + .try_into() + .map_err(|_| ParseError::InvalidQeReportSignature)?, + qe_authentication_data, + qe_certification_data, + }) + } + + fn verify( + &self, + attestation_key: &[u8], + tcb: &TcbInfo, + crl: &crate::cert::Crl, + now: u64, + ) -> Result<(), VerificationError> { + let hash = { + let mut hasher = Sha256::new(); + hasher.update(attestation_key); + hasher.update(self.qe_authentication_data.data.as_slice()); + hasher.finalize() + }; + + let actual = [&hash[..], &[0u8; HASH_PADDING_SIZE]].concat(); + let expected = &self.qe_report[QE_REPORT_SIZE - REPORT_DATA_SIZE..]; + + if actual != *expected { + return Err(VerificationError::FailedVerification); + } + + self.qe_certification_data.verify( + &self.qe_report, + &self.qe_report_signature, + attestation_key, + tcb, + crl, + now, + ) + } +} + +pub type TcbSvn = [u8; TCB_SVN_COUNT]; +pub type PceSvn = u16; + +#[derive(Debug)] +struct QuoteBodyV4 { + /// Describes the TCB of TDX + tee_tcb_svn: TcbSvn, + /// Measurement of the TDX Module. + _mrseam: [u8; BODY_MRSEAM_SIZE], + /// Zero for the Intel® TDX Module. + _mrsignerseam: [u8; BODY_MRSIGNERSEAM_SIZE], + /// Must be zero for TDX 1.0 + _seamattributes: [u8; BODY_SEAMATTRIBUTES_SIZE], + /// TD Attributes + tdattributes: [u8; BODY_TDATTRIBUTES_SIZE], + /// XFAM (eXtended Features Available Mask) is + /// defined as a 64b bitmap, which has the same + /// format as XCR0 or IA32_XSS MSR. + _xfam: [u8; BODY_XFAM_SIZE], + /// Measurement of the initial contents of the TD. + _mrtd: [u8; BODY_MRTD_SIZE], + /// Software-defined ID for non-owner-defined + /// configuration of the TD, e.g., runtime or OS configuration. + _mrconfigid: [u8; BODY_MRCONFIGID_SIZE], + /// Software-defined ID for the TD's owner + _mrowner: [u8; BODY_MROWNER_SIZE], + /// Software-defined ID for owner-defined + /// configuration of the TD, e.g., specific to the + /// workload rather than the runtime or OS. + _mrownerconfig: [u8; BODY_MROWNERCONFIG_SIZE], + /// Runtime extendable measurement register + _rtmr0: [u8; BODY_RTMR_SIZE], + /// Runtime extendable measurement register + _rtmr1: [u8; BODY_RTMR_SIZE], + /// Runtime extendable measurement register + _rtmr2: [u8; BODY_RTMR_SIZE], + /// Runtime extendable measurement register + _rtmr3: [u8; BODY_RTMR_SIZE], + /// Each TD Quote is based on a TD Report. The + /// TD is free to provide 64 bytes of custom data + /// to a TD Report. For instance, this space can be + /// used to hold a nonce, a public key, or a hash + /// of a larger block of data. + _reportdata: [u8; BODY_REPORTDATA_SIZE], +} + +impl QuoteBodyV4 { + fn from_bytes(input: &[u8]) -> Result { + if input.len() < QUOTE_BODY_SIZE { + return Err(ParseError::InvalidBody); + } + Ok(QuoteBodyV4 { + tee_tcb_svn: input[BODY_TEE_TCB_SVN_OFFSET..BODY_MRSEAM_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrseam: input[BODY_MRSEAM_OFFSET..BODY_MRSIGNERSEAM_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrsignerseam: input[BODY_MRSIGNERSEAM_OFFSET..BODY_SEAMATTRIBUTES_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _seamattributes: input[BODY_SEAMATTRIBUTES_OFFSET..BODY_TDATTRIBUTES_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + tdattributes: input[BODY_TDATTRIBUTES_OFFSET..BODY_XFAM_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _xfam: input[BODY_XFAM_OFFSET..BODY_MRTD_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrtd: input[BODY_MRTD_OFFSET..BODY_MRCONFIGID_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrconfigid: input[BODY_MRCONFIGID_OFFSET..BODY_MROWNER_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrowner: input[BODY_MROWNER_OFFSET..BODY_MROWNERCONFIG_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _mrownerconfig: input[BODY_MROWNERCONFIG_OFFSET..BODY_RTMR0_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _rtmr0: input[BODY_RTMR0_OFFSET..BODY_RTMR1_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _rtmr1: input[BODY_RTMR1_OFFSET..BODY_RTMR2_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _rtmr2: input[BODY_RTMR2_OFFSET..BODY_RTMR3_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _rtmr3: input[BODY_RTMR3_OFFSET..BODY_REPORTDATA_OFFSET] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + _reportdata: input[BODY_REPORTDATA_OFFSET..QUOTE_BODY_SIZE] + .try_into() + .map_err(|_| ParseError::InvalidBody)?, + }) + } + + fn to_bytes(&self, output: &mut [u8]) { + output[BODY_TEE_TCB_SVN_OFFSET..BODY_MRSEAM_OFFSET].copy_from_slice(&self.tee_tcb_svn); + output[BODY_MRSEAM_OFFSET..BODY_MRSIGNERSEAM_OFFSET].copy_from_slice(&self._mrseam); + output[BODY_MRSIGNERSEAM_OFFSET..BODY_SEAMATTRIBUTES_OFFSET] + .copy_from_slice(&self._mrsignerseam); + output[BODY_SEAMATTRIBUTES_OFFSET..BODY_TDATTRIBUTES_OFFSET] + .copy_from_slice(&self._seamattributes); + output[BODY_TDATTRIBUTES_OFFSET..BODY_XFAM_OFFSET].copy_from_slice(&self.tdattributes); + output[BODY_XFAM_OFFSET..BODY_MRTD_OFFSET].copy_from_slice(&self._xfam); + output[BODY_MRTD_OFFSET..BODY_MRCONFIGID_OFFSET].copy_from_slice(&self._mrtd); + output[BODY_MRCONFIGID_OFFSET..BODY_MROWNER_OFFSET].copy_from_slice(&self._mrconfigid); + output[BODY_MROWNER_OFFSET..BODY_MROWNERCONFIG_OFFSET].copy_from_slice(&self._mrowner); + output[BODY_MROWNERCONFIG_OFFSET..BODY_RTMR0_OFFSET].copy_from_slice(&self._mrownerconfig); + output[BODY_RTMR0_OFFSET..BODY_RTMR1_OFFSET].copy_from_slice(&self._rtmr0); + output[BODY_RTMR1_OFFSET..BODY_RTMR2_OFFSET].copy_from_slice(&self._rtmr1); + output[BODY_RTMR2_OFFSET..BODY_RTMR3_OFFSET].copy_from_slice(&self._rtmr2); + output[BODY_RTMR3_OFFSET..BODY_REPORTDATA_OFFSET].copy_from_slice(&self._rtmr3); + output[BODY_REPORTDATA_OFFSET..QUOTE_BODY_SIZE].copy_from_slice(&self._reportdata); + } +} + +/// A parsed Intel TDX Quote v4. +pub struct QuoteV4 { + header: QuoteHeader, + body: QuoteBodyV4, + /// Size of the Quote Signature Data structure + _quote_signature_data_len: u32, + /// Version 4 of the ECDSA 256Bit Signature Data Structure + quote_signature_data: QuoteSignatureData, +} + +#[derive(Debug)] +struct QeCertificationData { + certification_data_type: u16, + _size: u32, + certification_data: Vec, +} + +impl QeCertificationData { + fn from_bytes(input: &[u8]) -> Result { + if input.len() < CERT_DATA_HEADER_SIZE { + return Err(ParseError::InvalidCertificationData); + } + let certification_data_type = u16::from_le_bytes( + input[..CERT_DATA_TYPE_FIELD_SIZE] + .try_into() + .expect("length checked above"), + ); + let size = u32::from_le_bytes( + input[CERT_DATA_TYPE_FIELD_SIZE..CERT_DATA_HEADER_SIZE] + .try_into() + .expect("length checked above"), + ); + if input.len() < CERT_DATA_HEADER_SIZE + (size as usize) { + return Err(ParseError::InvalidCertificationData); + } + let certification_data = + input[CERT_DATA_HEADER_SIZE..CERT_DATA_HEADER_SIZE + (size as usize)].to_vec(); + if certification_data_type != CERT_DATA_TYPE_PCK_CHAIN + && certification_data_type != CERT_DATA_TYPE_QE_REPORT + { + return Err(ParseError::UnsupportedCertificationDataType); + } + + Ok(QeCertificationData { + certification_data_type, + _size: size, + certification_data, + }) + } + + fn extract_tcb_info( + data: &[u8], + oid: ObjectIdentifier, + ) -> Result<(TcbSvn, PceSvn), crate::cert::CertificateError> { + let mut tcb = [0u8; TCB_SVN_COUNT]; + let mut offset = 0; + + // The data is 17 concatenated ASN.1 sequences (16 TCB SVN + 1 PCE SVN) + // Each sequence contains an OID and an integer value + // Parse each sequence, advancing by the actual encoded length + + // TCB SVN values (first 16 sequences) + for t in tcb.iter_mut() { + let (value, seq_len) = crate::cert::parse_oid_value_pair(&data[offset..], &oid)?; + *t = value[0]; + offset += seq_len; + } + + // PCE SVN (17th sequence) + let (pce_buf, _) = crate::cert::parse_oid_value_pair(&data[offset..], &oid)?; + + let pce: u16 = match pce_buf.len() { + 1 => pce_buf[0].into(), + 2 => u16::from_le_bytes( + pce_buf[0..2] + .try_into() + .map_err(|_| crate::cert::CertificateError::ExtensionNotFound)?, + ), + _ => return Err(crate::cert::CertificateError::ExtensionNotFound), + }; + Ok((tcb, pce)) + } + + fn verify( + &self, + data: &[u8], + signature: &[u8], + attestation_key: &[u8], + tcb: &TcbInfo, + crl: &crate::cert::Crl, + now: u64, + ) -> Result<(), VerificationError> { + match self.certification_data_type { + CERT_DATA_TYPE_PCK_CHAIN => { + let cert = crate::cert::verify_pem_cert_chain( + &self.certification_data, + Some(crate::intel::ROOT_CERT), + Some(crl), + now, + ) + .map_err(|_| VerificationError::PKCChain)?; + + let sgx_ext = crate::cert::get_ext( + &cert, + spki::ObjectIdentifier::new(INTEL_SGX_OID).expect("Cannot decode OID"), + ) + .map_err(|_| VerificationError::CannotExtractIntelExtensions)?; + + let fmspc = crate::cert::extract_field( + sgx_ext, + spki::ObjectIdentifier::new(INTEL_FMSPC_OID).expect("Cannot decode OID"), + ) + .map_err(|_| VerificationError::CannotExtractIntelExtensions)?; + + let mut tcb_buf = [0u8; FMSPC_SIZE]; + hex::decode_to_slice(tcb.fmspc.clone(), &mut tcb_buf) + .map_err(|_| VerificationError::FmspcMismatch)?; + if tcb_buf != fmspc { + return Err(VerificationError::FmspcMismatch); + } + + let tcb_oid = + spki::ObjectIdentifier::new(INTEL_TCB_OID).expect("Cannot decode OID"); + let cert_tcb = crate::cert::extract_field(sgx_ext, tcb_oid) + .map_err(|_| VerificationError::CannotExtractIntelExtensions)?; + let (cert_tcb, cert_pce) = Self::extract_tcb_info(cert_tcb, tcb_oid) + .map_err(|_| VerificationError::CannotExtractIntelExtensions)?; + let (tcb_status, pce_svn) = + crate::intel::collaterals::compare_sgx_tcb_levels(&cert_tcb, &tcb.tcb_levels); + if tcb_status >= TcbStatus::Revoked { + return Err(VerificationError::BadTcbStatus(tcb_status)); + } + if cert_pce < pce_svn { + return Err(VerificationError::BadPceStatus); + } + + crate::cert::verify_signature(&cert, data, signature) + .map_err(|_| VerificationError::BadSignature)?; + } + + CERT_DATA_TYPE_QE_REPORT => { + let qe_report_certification_data = + QeReportCertificationData::from_bytes(&self.certification_data[..]) + .map_err(|_| VerificationError::InvalidQeReportData)?; + qe_report_certification_data.verify(attestation_key, tcb, crl, now)?; + } + _ => { + return Err(VerificationError::UnsupportedVerificationType); + } + }; + + Ok(()) + } +} + +#[derive(Debug)] +struct QuoteSignatureData { + quote_signature: [u8; ECDSA_SIGNATURE_SIZE], + ecdsa_attestation_key: [u8; ATTESTATION_KEY_SIZE], + qe_certification_data: QeCertificationData, +} + +impl QuoteSignatureData { + fn from_bytes(input: &[u8]) -> Result { + if input.len() < ECDSA_SIGNATURE_SIZE + ATTESTATION_KEY_SIZE { + return Err(ParseError::InvalidSignatureData); + } + let quote_signature: [u8; ECDSA_SIGNATURE_SIZE] = input[..ECDSA_SIGNATURE_SIZE] + .try_into() + .expect("length checked above"); + let ecdsa_attestation_key: [u8; ATTESTATION_KEY_SIZE] = input + [ECDSA_SIGNATURE_SIZE..ECDSA_SIGNATURE_SIZE + ATTESTATION_KEY_SIZE] + .try_into() + .expect("length checked above"); + let qe_certification_data = + QeCertificationData::from_bytes(&input[ECDSA_SIGNATURE_SIZE + ATTESTATION_KEY_SIZE..])?; + Ok(QuoteSignatureData { + quote_signature, + ecdsa_attestation_key, + qe_certification_data, + }) + } + + fn verify( + &self, + signed_data: &[u8], + tcb: &TcbInfo, + crl: &crate::cert::Crl, + now: u64, + ) -> Result<(), VerificationError> { + let key = + VerifyingKey::from_sec1_bytes(&[&[4], &self.ecdsa_attestation_key[..]].concat()[..]) + .map_err(|_| VerificationError::P256Error)?; + key.verify( + signed_data, + &Signature::from_bytes(&self.quote_signature.into()) + .map_err(|_| VerificationError::BadSignature)?, + ) + .map_err(|_| VerificationError::P256Error)?; + self.qe_certification_data.verify( + signed_data, + &self.quote_signature, + &self.ecdsa_attestation_key, + tcb, + crl, + now, + ) + } +} + +impl QuoteV4 { + fn from_bytes(input: &[u8]) -> Result { + // HEADER + let header = QuoteHeader::from_bytes(input)?; + if header.attestation_key_type != ATTESTATION_KEY_TYPE_ECDSA_256_P256 { + return Err(ParseError::UnsupportedAttestationKeyType); + } + if header.qe_vendor_id != INTEL_VENDOR_ID { + return Err(ParseError::UnsupportedVendorId); + } + + // BODY + let body = QuoteBodyV4::from_bytes(&input[QUOTE_HEADER_SIZE..])?; + let signature_data_offset = QUOTE_HEADER_SIZE + QUOTE_BODY_SIZE; + if input.len() < signature_data_offset + SIGNATURE_DATA_LEN_SIZE { + return Err(ParseError::InvalidSignatureData); + } + let quote_signature_data_len = u32::from_le_bytes( + input[signature_data_offset..signature_data_offset + SIGNATURE_DATA_LEN_SIZE] + .try_into() + .expect("length checked above"), + ); + let signature_data_start = signature_data_offset + SIGNATURE_DATA_LEN_SIZE; + let signature_data_end = signature_data_start + (quote_signature_data_len as usize); + if input.len() < signature_data_end { + return Err(ParseError::InvalidSignatureData); + } + let quote_signature_data: QuoteSignatureData = + QuoteSignatureData::from_bytes(&input[signature_data_start..signature_data_end])?; + + Ok(QuoteV4 { + header, + body, + _quote_signature_data_len: quote_signature_data_len, + quote_signature_data, + }) + } + + fn extended_checks(&self) -> Result<(), VerificationError> { + // Section 2.3.2 Extended TD Checks + + // Verify that all TD Under Debug flags are set to zero. + if self.body.tdattributes[TDATTRIBUTES_DEBUG_INDEX] != TDATTRIBUTES_DEBUG_DISABLED { + return Err(VerificationError::DebugModeEnabled); + } + Ok(()) + } + + fn check_tcb_level(&self, levels: &Vec) -> TcbStatus { + let (status, _) = + crate::intel::collaterals::compare_tcb_levels(&self.body.tee_tcb_svn, levels); + status + } + + /// Verify the quote against the provided TCB info and CRL. + pub fn verify( + &self, + tcb: &TcbInfo, + crl: &crate::cert::Crl, + now: u64, + ) -> Result<(), VerificationError> { + // As per Intel documentation this needs to: + // - Check the PCK Cert (signature chain). + // - Check if the PCK Cert is on the CRL. + // - Check the verification collaterals' cert signature chain, including PCK Cert Chain, TCB info chain and QE identity chain + // - Check if verification collaterals are on the CRL. + // - Check the TDQE Report signature and the contained AK hash using the PCK Cert. + // - (NOT IMPLEMENTED!) Check the measurements of the TDQE contained in the TDQE Report. + // - Check the signature of the TD Quote using the public key–part of the AK. Implicitly, this validated + // the TD and TDX Module measurements. + // - Evaluate the TDX TCB information contained in the TD Quote. + + let mut signed_data = [0u8; QUOTE_HEADER_SIZE + QUOTE_BODY_SIZE]; + self.header.to_bytes(&mut signed_data); + self.body.to_bytes(&mut signed_data[QUOTE_HEADER_SIZE..]); + + self.quote_signature_data + .verify(&signed_data[..], tcb, crl, now)?; + + let tcb_status = self.check_tcb_level(&tcb.tcb_levels); + if tcb_status >= TcbStatus::Revoked { + return Err(VerificationError::BadTcbStatus(tcb_status)); + } + + self.extended_checks()?; + Ok(()) + } +} + +/// Parse a quote from raw bytes. +pub fn parse_quote(input: &[u8]) -> Result { + QuoteV4::from_bytes(input) +} + +#[cfg(test)] +mod should { + use crate::intel::collaterals::parse_tcb_response; + use crate::intel::quote::{parse_quote, VerificationError}; + use assert_ok::assert_ok; + use rstest::rstest; + use std::{fs::File, io::Read}; + + fn load_file(path: &str) -> Vec { + let mut f = File::open(path).unwrap(); + let mut buf = Vec::new(); + f.read_to_end(&mut buf).unwrap(); + buf + } + + #[rstest] + #[case("assets/tests/intel/quote_b0.dat")] + #[case("assets/tests/intel/quote_90.dat")] + #[case("assets/tests/intel/quote_no_cert.dat")] + fn parses_quote(#[case] path: &str) { + let buf = load_file(path); + assert_ok!(parse_quote(&buf)); + } + + #[rstest] + #[should_panic(expected = "InvalidHeader")] + fn parse_truncated_header_fails() { + assert_ok!(parse_quote(&[0u8; 1])); + } + + #[rstest] + #[should_panic(expected = "UnsupportedAttestationKeyType")] + fn parse_invalid_header_fails() { + let quote_data = load_file("assets/tests/intel/quote_90.dat"); + let mut invalid_data = quote_data.clone(); + invalid_data[2] = 0xFF; + invalid_data[3] = 0xFF; + + assert_ok!(parse_quote(&invalid_data)); + } + + #[rstest] + #[case( + "assets/tests/intel/quote_b0.dat", + "assets/tests/intel/tcb_info_b0.json" + )] + #[should_panic(expected = "BadTcbStatus")] + #[case( + "assets/tests/intel/quote_90.dat", + "assets/tests/intel/tcb_info_90.json" + )] + fn verify_quote(#[case] quote_path: &str, #[case] coll_path: &str) { + let quote_buf = load_file(quote_path); + let q = parse_quote("e_buf).unwrap(); + + let tcb_buf = load_file(coll_path); + let tcb = parse_tcb_response(&tcb_buf).unwrap(); + let crl: crate::cert::Crl = vec![]; + let now: u64 = 1769529377; // Tue Jan 27 2026 15:56:17 GMT+0000 + assert_ok!(q.verify(&tcb.tcb_info, &crl, now)); + } + + #[test] + fn reject_quote_wo_certificates() { + let quote_buf = load_file("assets/tests/intel/quote_no_cert.dat"); + let q = parse_quote("e_buf).unwrap(); + + let tcb_buf = load_file("assets/tests/intel/tcb_info_90.json"); + let tcb = parse_tcb_response(&tcb_buf).unwrap(); + let crl: crate::cert::Crl = vec![]; + let now: u64 = 1769529377; // Tue Jan 27 2026 15:56:17 GMT+0000 + assert_eq!( + q.verify(&tcb.tcb_info, &crl, now), + Err(VerificationError::PKCChain) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..98bebab --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,62 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! TEE attestation quote verification library. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(test), deny(clippy::unwrap_used))] +#![deny(missing_docs)] + +extern crate alloc; +use alloc::vec::Vec; + +mod cert; +mod intel; + +pub use crate::{ + cert::{CertificateError, Crl, RevokedCertId}, + intel::{CollateralError, ParseError, QuoteV4, TcbResponse, VerificationError}, +}; + +// ============================================================================= +// Generic +// ============================================================================= + +/// Parse a CRL from PEM data and validates its signature. +/// Returns: +/// - the most recent issue date for all the included CRLs +/// - a Vec of revoked certificates, identified by the (issuer, serial_number) pair. +pub fn parse_crl( + crl_pem: &Vec, + pck_certificate_chain_pem: &Vec, + root_cert: Option<&[u8]>, + now: u64, +) -> Result<(u64, Crl), CertificateError> { + cert::parse_crl(crl_pem, pck_certificate_chain_pem, root_cert, now) +} + +// ============================================================================= +// Intel Specific +// ============================================================================= + +/// Parse a TCB response from JSON bytes. +pub fn parse_tcb_response(input: &[u8]) -> Result { + intel::parse_tcb_response(input) +} + +/// Parse an Intel TDX quote from binary data. +pub fn parse_quote(input: &[u8]) -> Result { + intel::parse_quote(input) +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..e4d5433 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,16 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod intel; diff --git a/tests/intel/mod.rs b/tests/intel/mod.rs new file mode 100644 index 0000000..5698594 --- /dev/null +++ b/tests/intel/mod.rs @@ -0,0 +1,138 @@ +// Copyright 2025, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Integration tests for the tee-verifier crate. +//! +//! These tests verify the complete end-to-end workflow of parsing and +//! verifying Intel SGX/TDX attestation quotes, including certificate +//! chain validation, CRL checking, and TCB collateral verification. + +use std::fs::File; +use std::io::Read; + +use assert_ok::assert_ok; +use chrono::DateTime; + +use tee_verifier::{parse_crl, parse_quote, parse_tcb_response, Crl, VerificationError}; + +/// Helper function to load a file into a byte vector +fn load_file(path: &str) -> Vec { + let mut f = File::open(path).unwrap(); + let mut buf = Vec::new(); + f.read_to_end(&mut buf).unwrap(); + buf +} + +/// Helper function to load the Intel root certificate +fn load_root_cert() -> Vec { + load_file("assets/Intel_SGX_Provisioning_Certification_RootCA.cer") +} + +/// Helper function to parse a timestamp string to unix timestamp +fn parse_timestamp(ts: &str) -> u64 { + DateTime::parse_from_rfc3339(ts).unwrap().timestamp() as u64 +} + +// ============================================================================= +// End-to-End Quote Verification Tests +// ============================================================================= + +mod end_to_end { + use super::*; + + #[test] + fn verify_quote() { + // Load quote data + let quote_data = load_file("assets/tests/intel/quote_b0.dat"); + + // Load tcb data + let tcb_data = load_file("assets/tests/intel/tcb_info_b0.json"); + let tcb_chain = load_file("assets/tests/intel/tcb_info_b0.pem"); + + // Load Certificate Revocation List data + let crl_data = load_file("assets/tests/intel/crl.pem"); + let crl_chain = load_file("assets/tests/intel/crl_chain.pem"); + + // Load Intel Root CA + let root_cert = load_root_cert(); + + let now = parse_timestamp("2026-02-03T09:32:53Z"); + + let (_crl_time, crl) = parse_crl(&crl_data, &crl_chain, Some(&root_cert), now).unwrap(); + + // Verify that the tcb data is valid and signed + let tcb_response = parse_tcb_response(&tcb_data).unwrap(); + assert_ok!(tcb_response.verify(tcb_chain, &crl, now)); + + // Verify the quote + let quote = parse_quote("e_data).unwrap(); + assert_ok!(quote.verify(&tcb_response.tcb_info, &crl, now)); + } +} + +// ============================================================================= +// Quote Parsing Tests +// ============================================================================= + +mod quote_parsing { + use super::*; + + #[test] + fn parse_valid_quote_90() { + let quote_data = load_file("assets/tests/intel/quote_90.dat"); + assert_ok!(parse_quote("e_data)); + } +} + +// ============================================================================= +// Quote Verification Error Tests +// ============================================================================= + +mod quote_verification_errors { + use super::*; + + #[test] + fn verify_quote_without_certificates_fails() { + let quote_data = load_file("assets/tests/intel/quote_no_cert.dat"); + let tcb_data = load_file("assets/tests/intel/tcb_info_90.json"); + + let quote = parse_quote("e_data).unwrap(); + let tcb_response = parse_tcb_response(&tcb_data).unwrap(); + + let crl: Crl = vec![]; + let now = parse_timestamp("2026-02-03T09:32:53Z"); + + let result = quote.verify(&tcb_response.tcb_info, &crl, now); + assert!(matches!(result, Err(VerificationError::PKCChain))); + } + + #[test] + fn verify_quote_with_mismatched_fmspc_fails() { + // Load the quote for the FMSPC starting with 90... + let quote_data = load_file("assets/tests/intel/quote_90.dat"); + + // And the tcb data for the FMSPC starting with B0 + let tcb_data = load_file("assets/tests/intel/tcb_info_b0.json"); + + let quote = parse_quote("e_data).unwrap(); + let tcb_response = parse_tcb_response(&tcb_data).unwrap(); + + let crl: Crl = vec![]; + let now = parse_timestamp("2026-02-03T09:32:53Z"); + + let result = quote.verify(&tcb_response.tcb_info, &crl, now); + assert!(matches!(result, Err(VerificationError::FmspcMismatch))); + } +}