diff --git a/.changelog/6331.breaking.md b/.changelog/6331.breaking.md new file mode 100644 index 00000000000..598b92541e4 --- /dev/null +++ b/.changelog/6331.breaking.md @@ -0,0 +1 @@ +go/common/sgx/pcs/policy: Add FMSPC whitelist to quote policy diff --git a/.changelog/6336.trivial.md b/.changelog/6336.trivial.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/go/common/sgx/pcs/policy.go b/go/common/sgx/pcs/policy.go index de8394db4b0..ba846fadacc 100644 --- a/go/common/sgx/pcs/policy.go +++ b/go/common/sgx/pcs/policy.go @@ -16,6 +16,10 @@ type QuotePolicy struct { // valid. TCB bundles containing smaller values will be invalid. MinTCBEvaluationDataNumber uint32 `json:"min_tcb_evaluation_data_number" yaml:"min_tcb_evaluation_data_number"` + // FMSPCWhitelist is a list of hexadecimal encoded FMSPCs specifying which processor + // packages and platform instances are allowed. + FMSPCWhitelist []string `json:"fmspc_whitelist,omitempty" yaml:"fmspc_whitelist,omitempty"` + // FMSPCBlacklist is a list of hexadecimal encoded FMSPCs specifying which processor // packages and platform instances are blocked. FMSPCBlacklist []string `json:"fmspc_blacklist,omitempty" yaml:"fmspc_blacklist,omitempty"` diff --git a/go/common/sgx/pcs/quote.go b/go/common/sgx/pcs/quote.go index 99d13d924af..16e18cad98f 100644 --- a/go/common/sgx/pcs/quote.go +++ b/go/common/sgx/pcs/quote.go @@ -144,7 +144,8 @@ func (q *Quote) Verify(policy *QuotePolicy, ts time.Time, tcb *TCBBundle) (*sgx. policy = &QuotePolicy{ TCBValidityPeriod: 30, MinTCBEvaluationDataNumber: DefaultMinTCBEvaluationDataNumber, - FMSPCBlacklist: []string{}, + FMSPCWhitelist: make([]string, 0), + FMSPCBlacklist: make([]string, 0), } } diff --git a/go/common/sgx/pcs/quote_test.go b/go/common/sgx/pcs/quote_test.go index 97ee170c158..21b4efeee24 100644 --- a/go/common/sgx/pcs/quote_test.go +++ b/go/common/sgx/pcs/quote_test.go @@ -121,6 +121,29 @@ func TestQuoteV3_ECDSA_P256_PCK_CertificateChain(t *testing.T) { require.Error(err, "Quote verification should fail for invalid TCB evaluation data number") require.ErrorContains(err, "pcs/quote: failed to verify TCB bundle: pcs/tcb: failed to verify QE identity: pcs/tcb: invalid QE identity: pcs/tcb: invalid QE evaluation data number") + // Test whitelisted FMSPC. + quotePolicy = &QuotePolicy{ + TCBValidityPeriod: 90, + FMSPCWhitelist: []string{}, + } + _, err = quote.Verify(quotePolicy, now, &tcbBundle) + require.NoError(err, "Quote verification should succeed for whitelisted FMSPCs") + + quotePolicy = &QuotePolicy{ + TCBValidityPeriod: 90, + FMSPCWhitelist: []string{"00606A000000"}, + } + _, err = quote.Verify(quotePolicy, now, &tcbBundle) + require.NoError(err, "Quote verification should succeed for whitelisted FMSPCs") + + quotePolicy = &QuotePolicy{ + TCBValidityPeriod: 90, + FMSPCWhitelist: []string{"00606A000001"}, + } + _, err = quote.Verify(quotePolicy, now, &tcbBundle) + require.Error(err, "Quote verification should fail for non-whitelisted FMSPCs") + require.ErrorContains(err, "pcs/quote: failed to verify TCB bundle: pcs/tcb: failed to verify TCB info: pcs/tcb: invalid TCB info: pcs/tcb: FMSPC is not whitelisted") + // Test blacklisted FMSPC. quotePolicy = &QuotePolicy{ TCBValidityPeriod: 90, @@ -128,7 +151,7 @@ func TestQuoteV3_ECDSA_P256_PCK_CertificateChain(t *testing.T) { } _, err = quote.Verify(quotePolicy, now, &tcbBundle) require.Error(err, "Quote verification should fail for blacklisted FMSPCs") - require.ErrorContains(err, "pcs/quote: failed to verify TCB bundle: pcs/tcb: failed to verify TCB info: pcs/tcb: invalid TCB info: pcs/tcb: blacklisted FMSPC") + require.ErrorContains(err, "pcs/quote: failed to verify TCB bundle: pcs/tcb: failed to verify TCB info: pcs/tcb: invalid TCB info: pcs/tcb: FMSPC is blacklisted") // Test TCB info certificates missing. tcbBundle2 := TCBBundle{ diff --git a/go/common/sgx/pcs/tcb.go b/go/common/sgx/pcs/tcb.go index 054cb1e32f1..40df25631fe 100644 --- a/go/common/sgx/pcs/tcb.go +++ b/go/common/sgx/pcs/tcb.go @@ -270,11 +270,14 @@ func (ti *TCBInfo) validate(teeType TeeType, ts time.Time, policy *QuotePolicy) return fmt.Errorf("pcs/tcb: invalid TCB evaluation data number") } - // Validate FMSPC not blacklisted. - for _, blocked := range policy.FMSPCBlacklist { - if blocked == ti.FMSPC { - return fmt.Errorf("pcs/tcb: blacklisted FMSPC") - } + // Validate FMSPC is whitelisted. + if len(policy.FMSPCWhitelist) > 0 && !slices.Contains(policy.FMSPCWhitelist, ti.FMSPC) { + return fmt.Errorf("pcs/tcb: FMSPC is not whitelisted") + } + + // Validate FMSPC is not blacklisted. + if slices.Contains(policy.FMSPCBlacklist, ti.FMSPC) { + return fmt.Errorf("pcs/tcb: FMSPC is blacklisted") } return nil diff --git a/go/common/sgx/quote/quote_test.go b/go/common/sgx/quote/quote_test.go index 2f655a70c0a..3bfa885ca7c 100644 --- a/go/common/sgx/quote/quote_test.go +++ b/go/common/sgx/quote/quote_test.go @@ -15,9 +15,12 @@ pcs: disabled: false tcb_validity_period: 30 min_tcb_evaluation_data_number: 17 - fmspc_blacklist: + fmspc_whitelist: - "000000000000" - "00606A000000" + fmspc_blacklist: + - "000000000001" + - "00606A000001" ` var dec Policy err := yaml.Unmarshal([]byte(testCase1), &dec) @@ -27,7 +30,10 @@ pcs: require.EqualValues(false, dec.PCS.Disabled) require.EqualValues(30, dec.PCS.TCBValidityPeriod) require.EqualValues(17, dec.PCS.MinTCBEvaluationDataNumber) + require.Len(dec.PCS.FMSPCWhitelist, 2) + require.EqualValues("000000000000", dec.PCS.FMSPCWhitelist[0]) + require.EqualValues("00606A000000", dec.PCS.FMSPCWhitelist[1]) require.Len(dec.PCS.FMSPCBlacklist, 2) - require.EqualValues("000000000000", dec.PCS.FMSPCBlacklist[0]) - require.EqualValues("00606A000000", dec.PCS.FMSPCBlacklist[1]) + require.EqualValues("000000000001", dec.PCS.FMSPCBlacklist[0]) + require.EqualValues("00606A000001", dec.PCS.FMSPCBlacklist[1]) } diff --git a/go/upgrade/migrations/consensus_242.go b/go/upgrade/migrations/consensus_242.go index 1f88f1b0152..194776b8f0f 100644 --- a/go/upgrade/migrations/consensus_242.go +++ b/go/upgrade/migrations/consensus_242.go @@ -11,8 +11,10 @@ import ( // Consensus242 is the name of the upgrade that enables features introduced in Oasis Core 24.2. // // This upgrade includes: -// - The `MayQuery“ field in the CHURP SGX policy, which defines which enclave identities +// - The `MayQuery` field in the CHURP SGX policy, which defines which enclave identities // are allowed to query runtime key shares. +// - The `FMSPCWhitelist` field in the quote policy, which defines which processor packages +// and platform instances are allowed. // - An updated key manager policy update transaction that applies a new policy at the epoch // boundary. const Consensus242 = "consensus242" diff --git a/runtime/src/common/sgx/pcs/mod.rs b/runtime/src/common/sgx/pcs/mod.rs index ed03000464d..0c0ef5da0b6 100644 --- a/runtime/src/common/sgx/pcs/mod.rs +++ b/runtime/src/common/sgx/pcs/mod.rs @@ -41,6 +41,8 @@ pub enum Error { TCBMismatch, #[error("TCB evaluation data number is invalid")] TCBEvaluationDataNumberInvalid, + #[error("FMSPC is not whitelisted")] + NotWhitelistedFMSPC, #[error("FMSPC is blacklisted")] BlacklistedFMSPC, #[error("QE report is malformed")] @@ -217,19 +219,48 @@ mod tests { } #[test] - fn test_quote_blacklisted_fmscp() { + fn test_quote_whitelisted_fmscp() { // From Go implementation. const RAW_QUOTE_BUNDLE: &[u8] = include_bytes!("../../../../testdata/pcs_quote_bundle.cbor"); let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap(); + let now = Utc.timestamp_opt(1671497404, 0).unwrap(); + + let policy = &QuotePolicy { + ..Default::default() + }; + qb.verify(policy, now) + .expect("quote verification should succeed for whitelisted FMSPCs"); + + let policy = &QuotePolicy { + fmspc_whitelist: vec!["00606A000000".to_string()], + ..Default::default() + }; + qb.verify(policy, now) + .expect("quote verification should succeed for whitelisted FMSPCs"); + + let policy: &QuotePolicy = &QuotePolicy { + fmspc_whitelist: vec!["00606A000001".to_string()], + ..Default::default() + }; + qb.verify(policy, now) + .expect_err("quote verification should fail for non-whitelisted FMSPCs"); + } + #[test] + fn test_quote_blacklisted_fmscp() { + // From Go implementation. + const RAW_QUOTE_BUNDLE: &[u8] = + include_bytes!("../../../../testdata/pcs_quote_bundle.cbor"); + + let qb: QuoteBundle = cbor::from_slice(RAW_QUOTE_BUNDLE).unwrap(); let now = Utc.timestamp_opt(1671497404, 0).unwrap(); + let policy = &QuotePolicy { fmspc_blacklist: vec!["00606A000000".to_string()], ..Default::default() }; - qb.verify(policy, now) .expect_err("quote verification should fail for blacklisted FMSPCs"); } diff --git a/runtime/src/common/sgx/pcs/policy.rs b/runtime/src/common/sgx/pcs/policy.rs index 849f44fdf6e..cd855a24995 100644 --- a/runtime/src/common/sgx/pcs/policy.rs +++ b/runtime/src/common/sgx/pcs/policy.rs @@ -15,6 +15,11 @@ pub struct QuotePolicy { /// smaller values will be invalid. pub min_tcb_evaluation_data_number: u32, + /// A list of hexadecimal encoded FMSPCs specifying which processor packages and platform + /// instances are allowed. + #[cbor(optional)] + pub fmspc_whitelist: Vec, + /// A list of hexadecimal encoded FMSPCs specifying which processor packages and platform /// instances are blocked. #[cbor(optional)] @@ -31,6 +36,7 @@ impl Default for QuotePolicy { disabled: false, tcb_validity_period: 30, min_tcb_evaluation_data_number: DEFAULT_MIN_TCB_EVALUATION_DATA_NUMBER, + fmspc_whitelist: Vec::new(), fmspc_blacklist: Vec::new(), tdx: None, } diff --git a/runtime/src/common/sgx/pcs/tcb.rs b/runtime/src/common/sgx/pcs/tcb.rs index 3af12be9d89..41f3445b98d 100644 --- a/runtime/src/common/sgx/pcs/tcb.rs +++ b/runtime/src/common/sgx/pcs/tcb.rs @@ -227,12 +227,13 @@ impl TCBInfo { return Err(Error::TCBEvaluationDataNumberInvalid); } - // Validate FMSPC not blacklisted. - let blocked = policy - .fmspc_blacklist - .iter() - .any(|blocked| blocked == &self.fmspc); - if blocked { + // Validate FMSPC is whitelisted. + if !policy.fmspc_whitelist.is_empty() && !policy.fmspc_whitelist.contains(&self.fmspc) { + return Err(Error::NotWhitelistedFMSPC); + } + + // Validate FMSPC is not blacklisted. + if policy.fmspc_blacklist.contains(&self.fmspc) { return Err(Error::BlacklistedFMSPC); }