Skip to content

Commit

Permalink
Add verification of TCB data
Browse files Browse the repository at this point in the history
The TCB data retrieved from
<https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc={}>,
can now be verified to have the correct signature and time period.
  • Loading branch information
nick-mobilecoin committed May 10, 2023
1 parent 63d43a7 commit 5ce40ff
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 18 deletions.
6 changes: 5 additions & 1 deletion verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ repository = { workspace = true }
rust-version = { workspace = true }

[features]
alloc = ["pem-rfc7468/alloc", "dep:const-oid", "dep:p256", "dep:x509-cert", "dep:serde_json", "dep:serde"]
alloc = ["pem-rfc7468/alloc", "dep:const-oid", "dep:p256", "dep:x509-cert", "tcb"]
tcb = ["dep:p256", "dep:serde_json", "dep:serde", "dep:der", "dep:hex"]

[dependencies]
const-oid = { version = "0.9.2", default-features = false, optional = true }
der = { version = "0.7.5", default-features = false, optional = true }
displaydoc = { version = "0.2.1", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["serde", "alloc"], optional = true }
mc-sgx-core-types = "0.6.0"
p256 = { version = "0.13.0", default-features = false, features = ["ecdsa"], optional = true }
pem-rfc7468 = { version = "0.7.0", default-features = false, optional = true }
Expand All @@ -30,6 +33,7 @@ x509-cert = { version = "0.2.0", default-features = false, optional = true }
[dev-dependencies]
mc-sgx-core-sys-types = "0.6.0"
textwrap = "0.16.0"
x509-cert = { version = "0.2.0", default-features = false, features = ["pem"] }
yare = "1.0.2"

# At least one crate must have this configuration
Expand Down
3 changes: 3 additions & 0 deletions verifier/data/tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@
This was captured on 2023-05-10.
* `example_tcb.json` - JSON file containing the example TCB response from
<https://api.portal.trustedservices.intel.com/documentation#pcs-tcb-info-v4>.
* `tcb_signer.pem` - The signer certificate for TCB data from
<https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc={}>.
This was retrieved by looking at the header using `curl --include ...`.
16 changes: 16 additions & 0 deletions verifier/data/tests/tcb_signer.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
BgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG
A1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw
b3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD
VQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv
P+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju
ypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f
BEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz
LmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK
QEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG
SM49BAMCA0cAMEQCIB9C8wOAN/ImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj
ftbrNGsGU8YH211dRiYNoPPu19Zp/ze8JmhujB0oBw==
-----END CERTIFICATE-----
2 changes: 1 addition & 1 deletion verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

mod report_body;
mod struct_name;
#[cfg(feature = "alloc")]
#[cfg(feature = "tcb")]
mod tcb;
#[cfg(feature = "alloc")]
mod x509;
Expand Down
227 changes: 211 additions & 16 deletions verifier/src/tcb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,36 @@

extern crate alloc;

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use der::DateTime;
use p256::ecdsa::signature::Verifier;
use p256::ecdsa::{Signature, VerifyingKey};
use serde::Deserialize;
use serde_json::value::RawValue;

/// Error parsing TCB(Trusted Computing Base) info
#[derive(displaydoc::Display, Debug)]

Check warning on line 44 in verifier/src/tcb.rs

View check run for this annotation

Codecov / codecov/patch

verifier/src/tcb.rs#L44

Added line #L44 was not covered by tests
pub enum Error {
/// Error converting from DER
Der(der::Error),
/// Error parsing TCB(Trusted Computing Base) json info: {0}
Serde(serde_json::Error),
/// Error decoding the signature in the TCB data
SignatureDecodeError,
/// Error verifying the signature
SignatureVerification,
/// TCB info not yet valid
TcbInfoNotYetValid,
/// TCB info expired
TcbInfoExpired,
}

impl From<der::Error> for Error {
fn from(e: der::Error) -> Self {
Error::Der(e)
}
}

impl From<serde_json::Error> for Error {
Expand All @@ -49,8 +69,9 @@ impl From<serde_json::Error> for Error {
}
}

/// The `tcb_info` member of the TCB(Trusted Computing Base) data retrieved from
/// The `tcbInfo` member of the TCB(Trusted Computing Base) data retrieved from
/// <https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc={}>
/// The schema is available at <https://api.portal.trustedservices.intel.com/documentation#pcs-tcb-info-model-v3>
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TcbInfo {
Expand Down Expand Up @@ -116,17 +137,58 @@ pub struct TcbComponent {
/// <https://api.trustedservices.intel.com/sgx/certification/v4/tcb?fmspc={}>
///
/// Due to the way the TCB info is signed the contents should be provided as is.
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TcbInfoRaw<'a> {
tcb_info: &'a RawValue,
signature: &'a str,
pub struct TcbInfoRaw {
tcb_info: Box<RawValue>,
#[serde(with = "hex")]
signature: Vec<u8>,
}

impl TcbInfoRaw {
/// Verify the `tcbInfo` signature and time are valid.
///
/// # Arguments
/// - `key` - The public key to verify the `tcbInfo` signature with
/// - `time` - The current system time
/// This is expected to be generated by:
/// ```ignore
/// let time = DateTime::from_system_time(SystemTime::now()).unwrap();
/// ```
/// or equivalent
pub fn verify(self, key: &VerifyingKey, time: DateTime) -> Result<(), Error> {
self.verify_time(time)?;
self.verify_signature(key)?;
Ok(())
}

fn verify_signature(&self, key: &VerifyingKey) -> Result<(), Error> {
let tcb_info = self.tcb_info.get();
let signature =
Signature::try_from(&self.signature[..]).map_err(|_| Error::SignatureDecodeError)?;
key.verify(tcb_info.as_bytes(), &signature)
.map_err(|_| Error::SignatureVerification)?;
Ok(())
}

fn verify_time(&self, time: DateTime) -> Result<(), Error> {
let tcb_info = TcbInfo::try_from(self.tcb_info.get())?;
let issue_date = tcb_info.issue_date.parse::<DateTime>()?;
let next_update = tcb_info.next_update.parse::<DateTime>()?;
if time < issue_date {
Err(Error::TcbInfoNotYetValid)
} else if time >= next_update {
Err(Error::TcbInfoExpired)
} else {
Ok(())
}
}
}

impl<'a> TryFrom<&'a str> for TcbInfoRaw<'a> {
impl TryFrom<&str> for TcbInfoRaw {
type Error = Error;

fn try_from(value: &'a str) -> Result<Self, Self::Error> {
fn try_from(value: &str) -> Result<Self, Self::Error> {
let tcb_info: TcbInfoRaw = serde_json::from_str(value)?;
Ok(tcb_info)
}
Expand All @@ -136,15 +198,35 @@ impl<'a> TryFrom<&'a str> for TcbInfoRaw<'a> {
mod tests {
use super::*;
use alloc::vec;
use p256::ecdsa::VerifyingKey;
use serde_json::value::RawValue;
use x509_cert::{der::DecodePem, Certificate};
use yare::parameterized;

fn tcb_verifying_key() -> VerifyingKey {
let pem = include_str!("../data/tests/tcb_signer.pem");
let certificate = Certificate::from_pem(pem).expect("failed to parse PEM");
let key = VerifyingKey::from_sec1_bytes(
certificate
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.expect("Failed to parse public key"),
)
.expect("Failed to decode public key");

key
}

#[test]
fn raw_parse_info() {
// For this test we don't care what the contents of `tcb_info` is, just
// that we separate the signature from the tcb_info correctly.
// For this test we don't care what the contents of `tcbInfo` is, just
// that we separate the signature from the tcbInfo correctly.
let raw_info =
r#"{"tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"hello"}"#;
r#"{"tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"abcd"}"#;
let tcb_info = TcbInfoRaw::try_from(raw_info).expect("Failed to parse TCB info");
assert_eq!(tcb_info.signature, "hello");
assert_eq!(tcb_info.signature, vec![171, 205]);
assert_eq!(
tcb_info.tcb_info.get(),
r#"{"id":"SGX","version":3,"fmspc":"00906ED50000"}"#
Expand All @@ -153,7 +235,7 @@ mod tests {

#[test]
fn raw_two_signatures_errors() {
let raw_info = r#"{"tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"hello","signature":"fail"}"#;
let raw_info = r#"{"tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"hello","signature":"abcd"}"#;
assert!(matches!(
TcbInfoRaw::try_from(raw_info),
Err(Error::Serde(_))
Expand All @@ -162,7 +244,7 @@ mod tests {

#[test]
fn raw_two_tcb_infos_errors() {
let raw_info = r#"{"tcbInfo":"too many cooks","tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"hello"}"#;
let raw_info = r#"{"tcbInfo":"too many cooks","tcbInfo":{"id":"SGX","version":3,"fmspc":"00906ED50000"},"signature":"abcd"}"#;
assert!(matches!(
TcbInfoRaw::try_from(raw_info),
Err(Error::Serde(_))
Expand All @@ -171,12 +253,12 @@ mod tests {

#[test]
fn raw_nested_tcb_info_still_in_parent() {
let raw_info = r#"{"tcbInfo":{"tcb_info":"nested","version":3,"fmspc":"00906ED50000"},"signature":"hello"}"#;
let raw_info = r#"{"tcbInfo":{"tcbInfo":"nested","version":3,"fmspc":"00906ED50000"},"signature":"f012"}"#;
let tcb_info = TcbInfoRaw::try_from(raw_info).expect("Failed to parse TCB info");
assert_eq!(tcb_info.signature, "hello");
assert_eq!(tcb_info.signature, vec![240, 18]);
assert_eq!(
tcb_info.tcb_info.get(),
r#"{"tcb_info":"nested","version":3,"fmspc":"00906ED50000"}"#
r#"{"tcbInfo":"nested","version":3,"fmspc":"00906ED50000"}"#
);
}

Expand Down Expand Up @@ -336,4 +418,117 @@ mod tests {
];
assert_eq!(tcb.sgx_tcb_components, components);
}

#[parameterized(
at_issue_date = {"2023-05-10T13:43:27Z"},
one_secend_after_issue_date = {"2023-05-10T13:43:28Z"},
just_before_next_update = {"2023-06-09T13:43:26Z"},
)]
fn tcb_verification(time: &str) {
let key = tcb_verifying_key();
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

let time = time.parse::<DateTime>().expect("Failed to parse time");

assert_eq!(raw_tcb.verify(&key, time).is_ok(), true);
}

#[test]
fn fails_before_issue_date() {
let key = tcb_verifying_key();
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

let time = "2023-05-10T13:43:26Z"
.parse::<DateTime>()
.expect("Failed to parse time");

assert_eq!(raw_tcb.verify(&key, time).is_err(), true);
}

#[test]
fn fails_at_next_update() {
let key = tcb_verifying_key();
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

let time = "2023-06-09T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");

assert_eq!(raw_tcb.verify(&key, time).is_err(), true);
}

#[test]
fn signature_decode_error() {
let key = tcb_verifying_key();
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let time = "2023-05-10T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");
let mut raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

// Note enough bytes to decode to the Signature type
raw_tcb.signature.truncate(63);

assert_eq!(raw_tcb.verify(&key, time).is_err(), true);
}

#[test]
fn signature_wrong() {
let key = tcb_verifying_key();
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let time = "2023-05-10T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");
let mut raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

raw_tcb.signature[0] += 1;

assert_eq!(raw_tcb.verify(&key, time).is_err(), true);
}

#[test]
fn tcb_info_fails_to_parse_when_verifying() {
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let time = "2023-05-10T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");
let mut raw_tcb = TcbInfoRaw::try_from(tcb_json).expect("Failed to parse raw TCB");

// We need valid JSON, but not valid for the TcbInfo
let bad_tcb_info = raw_tcb.tcb_info.get().replace("pceId", "unknown_field");
raw_tcb.tcb_info = RawValue::from_string(bad_tcb_info).expect("Failed to create RawValue");

assert_eq!(raw_tcb.verify_time(time).is_err(), true);
}

#[test]
fn tcb_issue_time_fails_to_parse_when_verifying() {
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let time = "2023-05-10T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");

let bad_time_json = tcb_json.replace("2023-05-10", "2023-15-10");
let raw_tcb =
TcbInfoRaw::try_from(bad_time_json.as_ref()).expect("Failed to parse raw TCB");

assert_eq!(raw_tcb.verify_time(time).is_err(), true);
}

#[test]
fn tcb_next_upate_time_fails_to_parse_when_verifying() {
let tcb_json = include_str!("../data/tests/fmspc_00906ED50000_2023_05_10.json");
let time = "2023-05-10T13:43:27Z"
.parse::<DateTime>()
.expect("Failed to parse time");

let bad_time_json = tcb_json.replace("2023-06-09", "2023-16-09");
let raw_tcb =
TcbInfoRaw::try_from(bad_time_json.as_ref()).expect("Failed to parse raw TCB");

assert_eq!(raw_tcb.verify_time(time).is_err(), true);
}
}

0 comments on commit 5ce40ff

Please sign in to comment.