Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attestation quote verification should check both run-time and build-time measurement values #1303

Merged
merged 14 commits into from
Feb 20, 2025
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ runtime
- Non persistent TSS signer and x25519 keypair ([#1216](https://github.com/entropyxyz/entropy-core/pull/1216))
- Extract PCK certificate chain from quotes ([#1209](https://github.com/entropyxyz/entropy-core/pull/1209))
- Allow different versions for programs ([#1250](https://github.com/entropyxyz/entropy-core/pull/1250))
- Attestation quote verification should check both run-time and build-time measurement values ([#1303](https://github.com/entropyxyz/entropy-core/pull/1303))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this counts as breaking. Old builds of entropy-tss will no longer be compatible with the chain as it is built here - but that is not external-facing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we had a running network depending on this then yes this would be problematic. So yeah I'd mention it here


### Fixed

Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
26 changes: 23 additions & 3 deletions crates/shared/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@
//! TDX attestion related shared types and functions

use crate::X25519PublicKey;
use blake2::{Blake2b512, Digest};
use blake2::{Blake2b, Blake2b512, Digest};
use codec::{Decode, Encode};

/// The acceptable TDX measurement value for non-production chainspecs.
/// This is the measurement given in mock quotes. Mock quotes have all zeros for each of the 5
/// 48 bit measurement registers. The overall measurement is the Blake2b hash of these values.
/// So this is the Blake2b hash of 5 * 48 zero bytes.
pub const MEASUREMENT_VALUE_MOCK_QUOTE: [u8; 32] = [
91, 172, 96, 209, 130, 160, 167, 174, 152, 184, 193, 27, 88, 59, 117, 235, 74, 39, 194, 69,
147, 72, 129, 25, 224, 24, 189, 103, 224, 20, 107, 116,
];

/// Input data to be included in a TDX attestation
pub struct QuoteInputData(pub [u8; 64]);

Expand Down Expand Up @@ -115,7 +124,7 @@ pub enum VerifyQuoteError {
/// Hashed input data does not match what was expected
IncorrectInputData,
/// Unacceptable VM image running
BadMrtdValue,
BadMeasurementValue,
/// Cannot encode verifying key (PCK)
CannotEncodeVerifyingKey,
/// Cannot decode verifying key (PCK)
Expand All @@ -141,7 +150,7 @@ impl std::fmt::Display for VerifyQuoteError {
VerifyQuoteError::IncorrectInputData => {
write!(f, "Hashed input data does not match what was expected")
},
VerifyQuoteError::BadMrtdValue => write!(f, "Unacceptable VM image running"),
VerifyQuoteError::BadMeasurementValue => write!(f, "Unacceptable VM image running"),
VerifyQuoteError::CannotEncodeVerifyingKey => {
write!(f, "Cannot encode verifying key (PCK)")
},
Expand Down Expand Up @@ -196,3 +205,14 @@ pub fn verify_pck_certificate_chain(
.map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
Ok(provisioning_certification_key)
}

/// Create a measurement value by hashing together all measurement registers from quote data
pub fn compute_quote_measurement(quote: &tdx_quote::Quote) -> [u8; 32] {
let mut hasher = Blake2b::new();
hasher.update(quote.mrtd());
hasher.update(quote.rtmr0());
hasher.update(quote.rtmr1());
hasher.update(quote.rtmr2());
hasher.update(quote.rtmr3());
hasher.finalize().into()
}
12 changes: 6 additions & 6 deletions crates/threshold-signature-server/src/attestation/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use axum::{
use entropy_client::user::request_attestation;
use entropy_kvdb::kv_manager::KvManager;
use entropy_shared::{
attestation::{QuoteContext, QuoteInputData, VerifyQuoteError},
attestation::{compute_quote_measurement, QuoteContext, QuoteInputData, VerifyQuoteError},
OcwMessageAttestationRequest,
};
use parity_scale_codec::Decode;
Expand Down Expand Up @@ -216,16 +216,16 @@ pub async fn check_quote_measurement(
rpc: &LegacyRpcMethods<EntropyConfig>,
quote: &Quote,
) -> Result<(), QuoteMeasurementErr> {
let mrtd_value = quote.mrtd().to_vec();
let query = entropy::storage().parameters().accepted_mrtd_values();
let accepted_mrtd_values: Vec<_> = query_chain(api, rpc, query, None)
let measurement_value = compute_quote_measurement(quote).to_vec();
let query = entropy::storage().parameters().accepted_measurement_values();
let accepted_measurement_values: Vec<_> = query_chain(api, rpc, query, None)
.await?
.ok_or(QuoteMeasurementErr::NoMeasurementValues)?
.into_iter()
.map(|v| v.0)
.collect();
if !accepted_mrtd_values.contains(&mrtd_value) {
return Err(VerifyQuoteError::BadMrtdValue.into());
if !accepted_measurement_values.contains(&measurement_value) {
return Err(VerifyQuoteError::BadMeasurementValue.into());
};
Ok(())
}
10 changes: 5 additions & 5 deletions node/cli/src/chain_spec/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::chain_spec::{
get_account_id_from_seed, provisioning_certification_key, ChainSpec, MrtdValues,
get_account_id_from_seed, provisioning_certification_key, ChainSpec, MeasurementValues,
MEASUREMENT_VALUE_MOCK_QUOTE,
};
use crate::endowed_accounts::endowed_accounts_dev;

Expand Down Expand Up @@ -173,7 +174,7 @@ pub fn development_genesis_config(
String,
BoundedVecEncodedVerifyingKey,
)>,
accepted_mrtd_values: Option<MrtdValues>,
accepted_measurement_values: Option<MeasurementValues>,
) -> serde_json::Value {
// Note that any endowed_accounts added here will be included in the `elections` and
// `technical_committee` genesis configs. If you don't want that, don't push those accounts to
Expand Down Expand Up @@ -287,9 +288,8 @@ pub fn development_genesis_config(
max_instructions_per_programs: INITIAL_MAX_INSTRUCTIONS_PER_PROGRAM,
total_signers: TOTAL_SIGNERS,
threshold: SIGNER_THRESHOLD,
accepted_mrtd_values: accepted_mrtd_values.unwrap_or(vec![
BoundedVec::try_from([0; 48].to_vec()).unwrap(),
BoundedVec::try_from([1; 48].to_vec()).unwrap(),
accepted_measurement_values: accepted_measurement_values.unwrap_or(vec![
BoundedVec::try_from(MEASUREMENT_VALUE_MOCK_QUOTE.to_vec()).unwrap(),
]),
..Default::default()
},
Expand Down
10 changes: 6 additions & 4 deletions node/cli/src/chain_spec/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::chain_spec::{get_account_id_from_seed, provisioning_certification_key, ChainSpec};
use crate::chain_spec::{
get_account_id_from_seed, provisioning_certification_key, ChainSpec,
MEASUREMENT_VALUE_MOCK_QUOTE,
};
use crate::endowed_accounts::endowed_accounts_dev;

use entropy_runtime::{
Expand Down Expand Up @@ -258,9 +261,8 @@ pub fn integration_tests_genesis_config(
max_instructions_per_programs: INITIAL_MAX_INSTRUCTIONS_PER_PROGRAM,
total_signers: TOTAL_SIGNERS,
threshold: SIGNER_THRESHOLD,
accepted_mrtd_values: vec![
BoundedVec::try_from([0; 48].to_vec()).unwrap(),
BoundedVec::try_from([1; 48].to_vec()).unwrap(),
accepted_measurement_values: vec![
BoundedVec::try_from(MEASUREMENT_VALUE_MOCK_QUOTE.to_vec()).unwrap(),
],
..Default::default()
},
Expand Down
5 changes: 3 additions & 2 deletions node/cli/src/chain_spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod tdx_testnet;
pub mod testnet;

pub use entropy_runtime::{AccountId, RuntimeGenesisConfig, Signature};
pub use entropy_shared::attestation::MEASUREMENT_VALUE_MOCK_QUOTE;

use entropy_runtime::{Block, SessionKeys};
use grandpa_primitives::AuthorityId as GrandpaId;
Expand Down Expand Up @@ -220,5 +221,5 @@ pub fn authority_keys_from_seed(
)
}

/// Accepted build time measurement values for TDX attestation
pub type MrtdValues = Vec<BoundedVec<u8, ConstU32<48>>>;
/// Accepted measurement values for TDX attestation
pub type MeasurementValues = Vec<BoundedVec<u8, ConstU32<32>>>;
8 changes: 2 additions & 6 deletions node/cli/src/chain_spec/tdx_testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ use sp_core::sr25519;
use sp_runtime::BoundedVec;

/// The build time measurement value from the current entropy-tss VM images
const ACCEPTED_MRTD: [u8; 48] = [
145, 235, 43, 68, 209, 65, 212, 236, 224, 159, 12, 117, 194, 197, 61, 36, 122, 60, 104, 237,
215, 250, 254, 138, 53, 32, 201, 66, 166, 4, 164, 7, 222, 3, 174, 109, 197, 248, 127, 39, 66,
139, 37, 56, 135, 49, 24, 183,
];
const ACCEPTED_MEASUREMENT: [u8; 32] = [0; 32];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to add this in a followup. This chainspec is currently not being used anywhere, so should not be an issue to leave it like this for now.


lazy_static::lazy_static! {
/// This is the PCK from the certificates of the current TDX machine we are using for testing
Expand Down Expand Up @@ -92,7 +88,7 @@ pub fn tdx_testnet_config() -> ChainSpec {
vec![],
get_account_id_from_seed::<sr25519::Public>("Alice"),
tdx_devnet_four_node_initial_tss_servers(),
Some(vec![BoundedVec::try_from(ACCEPTED_MRTD.to_vec()).unwrap()]),
Some(vec![BoundedVec::try_from(ACCEPTED_MEASUREMENT.to_vec()).unwrap()]),
))
.build()
}
10 changes: 6 additions & 4 deletions node/cli/src/chain_spec/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::chain_spec::{get_account_id_from_seed, provisioning_certification_key, ChainSpec};
use crate::chain_spec::{
get_account_id_from_seed, provisioning_certification_key, ChainSpec,
MEASUREMENT_VALUE_MOCK_QUOTE,
};
use crate::endowed_accounts::endowed_testnet_accounts;

use entropy_runtime::{
Expand Down Expand Up @@ -459,9 +462,8 @@ pub fn testnet_genesis_config(
max_instructions_per_programs: INITIAL_MAX_INSTRUCTIONS_PER_PROGRAM,
total_signers: TOTAL_SIGNERS,
threshold: SIGNER_THRESHOLD,
accepted_mrtd_values: vec![
BoundedVec::try_from([0; 48].to_vec()).unwrap(),
BoundedVec::try_from([1; 48].to_vec()).unwrap(),
accepted_measurement_values: vec![
BoundedVec::try_from(MEASUREMENT_VALUE_MOCK_QUOTE.to_vec()).unwrap(),
],
..Default::default()
},
Expand Down
20 changes: 12 additions & 8 deletions pallets/attestation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ mod tests;
#[frame_support::pallet]
pub mod pallet {
use entropy_shared::attestation::{
verify_pck_certificate_chain, AttestationHandler, QuoteContext, QuoteInputData,
VerifyQuoteError,
compute_quote_measurement, verify_pck_certificate_chain, AttestationHandler, QuoteContext,
QuoteInputData, VerifyQuoteError,
};
use frame_support::pallet_prelude::*;
use frame_support::traits::Randomness;
Expand Down Expand Up @@ -135,7 +135,7 @@ pub mod pallet {
/// The given account doesn't have a registered provisioning certification key.
NoPCKForAccount,
/// Unacceptable VM image running
BadMrtdValue,
BadMeasurementValue,
/// Cannot encode verifying key (PCK)
CannotEncodeVerifyingKey,
/// Cannot decode verifying key (PCK)
Expand Down Expand Up @@ -229,11 +229,15 @@ pub mod pallet {
VerifyQuoteError::IncorrectInputData
);

// Check build-time measurement matches a current-supported release of entropy-tss
let mrtd_value = BoundedVec::try_from(quote.mrtd().to_vec())
.map_err(|_| VerifyQuoteError::BadMrtdValue)?;
let accepted_mrtd_values = pallet_parameters::Pallet::<T>::accepted_mrtd_values();
ensure!(accepted_mrtd_values.contains(&mrtd_value), VerifyQuoteError::BadMrtdValue);
// Check measurement matches a current-supported release of entropy-tss
let measurement = BoundedVec::try_from(compute_quote_measurement(&quote).to_vec())
.map_err(|_| VerifyQuoteError::BadMeasurementValue)?;
let accepted_measurement_values =
pallet_parameters::Pallet::<T>::accepted_measurement_values();
ensure!(
accepted_measurement_values.contains(&measurement),
VerifyQuoteError::BadMeasurementValue
);

let pck = verify_pck_certificate_chain(&quote)?;

Expand Down
6 changes: 5 additions & 1 deletion pallets/attestation/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use entropy_shared::attestation::MEASUREMENT_VALUE_MOCK_QUOTE;
use frame_election_provider_support::{
bounds::{ElectionBounds, ElectionBoundsBuilder},
onchain, SequentialPhragmen, VoteWeight,
Expand Down Expand Up @@ -381,7 +382,10 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
max_instructions_per_programs: 5u64,
total_signers: 3u8,
threshold: 2u8,
accepted_mrtd_values: vec![BoundedVec::try_from([0; 48].to_vec()).unwrap()],
accepted_measurement_values: vec![BoundedVec::try_from(
MEASUREMENT_VALUE_MOCK_QUOTE.to_vec(),
)
.unwrap()],
_config: Default::default(),
};

Expand Down
8 changes: 4 additions & 4 deletions pallets/parameters/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ benchmarks! {
assert_last_event::<T>(Event::SignerInfoChanged{ signer_info }.into());
}

change_accepted_mrtd_values {
change_accepted_measurement_values {
let origin = T::UpdateOrigin::try_successful_origin().unwrap();
let accepted_mrtd_values = vec![BoundedVec::try_from([0; 48].to_vec()).unwrap()];
let accepted_measurement_values = vec![BoundedVec::try_from([0; 32].to_vec()).unwrap()];
}: {
assert_ok!(
<Parameters<T>>::change_accepted_mrtd_values(origin, accepted_mrtd_values.clone())
<Parameters<T>>::change_accepted_measurement_values(origin, accepted_measurement_values.clone())
);
}
verify {
assert_last_event::<T>(Event::AcceptedMrtdValuesChanged{ accepted_mrtd_values }.into());
assert_last_event::<T>(Event::AcceptedMeasurementValuesChanged{ accepted_measurement_values }.into());
}

impl_benchmark_test_suite!(Parameters, crate::mock::new_test_ext(), crate::mock::Runtime);
Expand Down
Loading