From 5afd3f8921dfc874af594a73cecd764ffa338af1 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 28 Oct 2024 12:47:51 +0100 Subject: [PATCH] Handle PCK certificates (#1068) * Add x509-parser dependency and helper fns * Add pck cert chain parser * Doccomments * Use x509 crate that works with no-std * Tidy, comments * WIP - handle PCK certs in staking pallet * Rm pck cert stuff from attestation pallet * Fix mock pck cert chain verifying * Taplo * Compress verifying key * Add missing import * Add missing implementations of PckCertVerifyier * Fix staking pallet benchmarks * Validate cert chain of arbitrary length * Tidy, fix tests * Lockfile * Clippy * Error handling * Add test for production cert verifyer * Rm commented code in test * Changelog * update metadata * Hopefully fix staking extension pallet benchmarking for validate extrinsic * Update pallets/staking/src/lib.rs Co-authored-by: Hernando Castano * Update pallets/staking/src/tests.rs Co-authored-by: Hernando Castano * Update pallets/staking/src/pck/production.rs Co-authored-by: Hernando Castano * Update pallets/staking/src/pck/production.rs Co-authored-by: Hernando Castano * Typo in struct name and improve test readablilty * Rm calls to mock_attest_validate in staking pallet test as fn is now removed * Handle errors in production verify_cert fn * Doccomments * Alphabetically sort runtime config types * Changelog * derive serialize and deserialize for JoiningServerInfo * Rename struct (typo) * Make it clearer what is happenning with the PCK generation in staking pallet benchmark * Use intel root cert from web link and link to it in doccomments * Fix staking pallet benchmarks in always generate a mock quote before validate * Slightly improve error conversion --------- Co-authored-by: Jesse Abramowitz Co-authored-by: Hernando Castano --- CHANGELOG.md | 4 + Cargo.lock | 117 +++++++++++++++++ crates/client/entropy_metadata.scale | Bin 209375 -> 209698 bytes pallets/attestation/src/mock.rs | 1 + pallets/propagation/src/mock.rs | 1 + pallets/registry/src/mock.rs | 1 + pallets/staking/Cargo.toml | 4 + pallets/staking/src/benchmarking.rs | 121 ++++++++++-------- pallets/staking/src/lib.rs | 50 +++++++- pallets/staking/src/mock.rs | 2 + ..._SGX_Provisioning_Certification_RootCA.cer | Bin 0 -> 659 bytes pallets/staking/src/pck/mock.rs | 56 ++++++++ pallets/staking/src/pck/mod.rs | 58 +++++++++ pallets/staking/src/pck/production.rs | 110 ++++++++++++++++ pallets/staking/src/tests.rs | 84 ++++++------ pallets/staking/test_pck_certs/pck_cert.der | Bin 0 -> 1269 bytes .../test_pck_certs/platform_pcs_cert.der | Bin 0 -> 666 bytes runtime/src/lib.rs | 1 + 18 files changed, 516 insertions(+), 94 deletions(-) create mode 100644 pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer create mode 100644 pallets/staking/src/pck/mock.rs create mode 100644 pallets/staking/src/pck/mod.rs create mode 100644 pallets/staking/src/pck/production.rs create mode 100644 pallets/staking/test_pck_certs/pck_cert.der create mode 100644 pallets/staking/test_pck_certs/platform_pcs_cert.der diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ef94fb8..be9f22026 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,14 @@ At the moment this project **does not** adhere to structure, and the `NodeInfoChanged` event were removed from the Staking Extension pallet. The `AttestationHandler` config type was added to the Staking Extension pallet. The `KeyProvider` and `AttestationQueue` config types were removed from the Attestation pallet. +- In [#1068](https://github.com/entropyxyz/entropy-core/pull/1068) an extra type `PckCertChainVerifier` + was added to the staking extension pallet's `Config` trait. - In [#1134](https://github.com/entropyxyz/entropy-core/pull/1134/) the ```no-sync``` option was removed + ### Changed - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104)) - Change attestation flow to be pull based ([#1109](https://github.com/entropyxyz/entropy-core/pull/1109/)) +- Handle PCK certificates ([#1068](https://github.com/entropyxyz/entropy-core/pull/1068)) - Remove declare synced ([#1134](https://github.com/entropyxyz/entropy-core/pull/1134/)) ## [0.3.0-rc.1](https://github.com/entropyxyz/entropy-core/compare/release/v0.2.0...release/v0.3.0-rc.1) - 2024-10-04 diff --git a/Cargo.lock b/Cargo.lock index 23fa0e60a..2ad521315 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,6 +2055,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "der_derive", + "flagset", "zeroize", ] @@ -2072,6 +2074,17 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "deranged" version = "0.3.11" @@ -3170,6 +3183,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flagset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" + [[package]] name = "flate2" version = "1.0.28" @@ -6423,6 +6442,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -6694,8 +6730,10 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ + "ecdsa", "elliptic-curve", "primeorder", + "sha2 0.10.8", ] [[package]] @@ -7430,6 +7468,7 @@ dependencies = [ "frame-support 29.0.2", "frame-system", "log", + "p256", "pallet-bags-list", "pallet-balances", "pallet-parameters", @@ -7438,6 +7477,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", + "rand", "rand_chacha 0.3.1", "rand_core 0.6.4", "scale-info", @@ -7449,7 +7489,9 @@ dependencies = [ "sp-runtime 32.0.0", "sp-staking 27.0.0", "sp-std 14.0.0", + "spki", "tdx-quote", + "x509-verify", ] [[package]] @@ -7957,6 +7999,17 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -8971,6 +9024,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -16532,6 +16605,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", +] + +[[package]] +name = "x509-ocsp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e54e695a31f0fecb826cf59ae2093c941d7ef932a1f8508185dd23b29ce2e2e" +dependencies = [ + "const-oid", + "der", + "spki", + "x509-cert", +] + [[package]] name = "x509-parser" version = "0.14.0" @@ -16550,6 +16646,27 @@ dependencies = [ "time", ] +[[package]] +name = "x509-verify" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605feeee7186660fcb5ddaa1263b6d3b9ba16f128a6b48cb5e24ef7a241e43ab" +dependencies = [ + "const-oid", + "der", + "ecdsa", + "ed25519-dalek", + "k256", + "p256", + "p384", + "rsa", + "sha2 0.10.8", + "signature", + "spki", + "x509-cert", + "x509-ocsp", +] + [[package]] name = "yamux" version = "0.10.2" diff --git a/crates/client/entropy_metadata.scale b/crates/client/entropy_metadata.scale index c8e04d4635263100651d3b91fb5a7e923f53f639..fcfbd8800746eaa47293aebd17012d87cefbfd01 100644 GIT binary patch delta 9405 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 KtPwjD;eP?2%rDXa diff --git a/pallets/attestation/src/mock.rs b/pallets/attestation/src/mock.rs index f75f82b58..c546530e4 100644 --- a/pallets/attestation/src/mock.rs +++ b/pallets/attestation/src/mock.rs @@ -315,6 +315,7 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; + type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/propagation/src/mock.rs b/pallets/propagation/src/mock.rs index 31e4e1600..424010c8b 100644 --- a/pallets/propagation/src/mock.rs +++ b/pallets/propagation/src/mock.rs @@ -309,6 +309,7 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; + type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/registry/src/mock.rs b/pallets/registry/src/mock.rs index 4f20fe263..ad5b26c3b 100644 --- a/pallets/registry/src/mock.rs +++ b/pallets/registry/src/mock.rs @@ -306,6 +306,7 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; + type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index 5729b1850..185002c38 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -28,6 +28,10 @@ sp-runtime ={ version="32.0.0", default-features=false } sp-staking ={ version="27.0.0", default-features=false } sp-std ={ version="14.0.0", default-features=false } sp-consensus-babe ={ version="0.33.0", default-features=false } +x509-verify ={ version="0.4.6", features=["x509"] } +spki ="0.7.3" +p256 ={ version="0.13.2", default-features=false, features=["ecdsa"] } +rand ={ version="0.8.5", default-features=false, features=["alloc"] } pallet-parameters={ version="0.3.0-rc.1", path="../parameters", default-features=false } entropy-shared={ version="0.3.0-rc.1", path="../../crates/shared", features=[ diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs index 126cd6fed..7969acbd3 100644 --- a/pallets/staking/src/benchmarking.rs +++ b/pallets/staking/src/benchmarking.rs @@ -16,6 +16,7 @@ //! Benchmarking setup for pallet-propgation #![allow(unused_imports)] use super::*; +use crate::pck::{signing_key_from_seed, MOCK_PCK_DERIVED_FROM_NULL_ARRAY}; #[allow(unused_imports)] use crate::Pallet as Staking; use entropy_shared::{AttestationHandler, MAX_SIGNERS}; @@ -71,6 +72,46 @@ pub fn create_validators( validators } +/// Sets up a mock quote and requests an attestation in preparation for calling the `validate` +/// extrinsic +fn prepare_attestation_for_validate( + threshold: T::AccountId, + x25519_public_key: [u8; 32], + endpoint: Vec, + block_number: u32, +) -> (Vec, JoiningServerInfo) { + let nonce = NULL_ARR; + let quote = { + let pck = signing_key_from_seed(NULL_ARR); + /// This is a randomly generated secret p256 ECDSA key - for mocking attestation + const ATTESTATION_KEY: [u8; 32] = [ + 167, 184, 203, 130, 240, 249, 191, 129, 206, 9, 200, 29, 99, 197, 64, 81, 135, 166, 59, + 73, 31, 27, 206, 207, 69, 248, 56, 195, 64, 92, 109, 46, + ]; + + let attestation_key = tdx_quote::SigningKey::from_bytes(&ATTESTATION_KEY.into()).unwrap(); + + let input_data = + entropy_shared::QuoteInputData::new(&threshold, x25519_public_key, nonce, block_number); + + tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0).as_bytes().to_vec() + }; + + let joining_server_info = JoiningServerInfo { + tss_account: threshold.clone(), + x25519_public_key, + endpoint, + // Since we are using the mock PckCertChainVerifier, this needs to be the same seed for + // generating the PCK as we used to sign the quote above + pck_certificate_chain: vec![NULL_ARR.to_vec()], + }; + + // We need to tell the attestation handler that we want a quote. This will let the system to + // know to expect one back when we call `validate()`. + T::AttestationHandler::request_quote(&threshold, nonce); + (quote, joining_server_info) +} + fn prep_bond_and_validate( validate_also: bool, caller: T::AccountId, @@ -91,22 +132,19 @@ fn prep_bond_and_validate( )); if validate_also { - let server_info = ServerInfo { - tss_account: threshold, + let block_number = 0; + let endpoint = vec![20, 20]; + let (quote, joining_server_info) = prepare_attestation_for_validate::( + threshold, x25519_public_key, - endpoint: vec![20, 20], - provisioning_certification_key: BoundedVec::with_max_capacity(), - }; - - // Note: This isn't a valid quote, but for testing benches this will pass. - // - // For actually running benches a valid quote will be required in the future. - let quote = [0; 32].to_vec(); + endpoint, + block_number, + ); assert_ok!(>::validate( RawOrigin::Signed(bonder.clone()).into(), ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), quote, )); @@ -114,7 +152,17 @@ fn prep_bond_and_validate( .or(Err(Error::::InvalidValidatorId)) .unwrap(); - ThresholdToStash::::insert(&server_info.tss_account, &validator_id); + ThresholdToStash::::insert(&joining_server_info.tss_account, &validator_id); + + let server_info = ServerInfo { + tss_account: joining_server_info.tss_account, + x25519_public_key: joining_server_info.x25519_public_key, + endpoint: joining_server_info.endpoint, + provisioning_certification_key: MOCK_PCK_DERIVED_FROM_NULL_ARRAY + .to_vec() + .try_into() + .unwrap(), + }; ThresholdServers::::insert(&validator_id, server_info); } } @@ -152,7 +200,7 @@ benchmarks! { endpoint: vec![20, 20], tss_account: _bonder.clone(), x25519_public_key: NULL_ARR, - provisioning_certification_key: BoundedVec::with_max_capacity(), + provisioning_certification_key: MOCK_PCK_DERIVED_FROM_NULL_ARRAY.to_vec().try_into().unwrap(), }; assert_last_event::(Event::::ThresholdAccountChanged(bonder, server_info).into()); } @@ -269,7 +317,6 @@ benchmarks! { .or(Err(Error::::InvalidValidatorId)) .unwrap(); - let block_number = 1; let nonce = NULL_ARR; let x25519_public_key = NULL_ARR; let endpoint = vec![]; @@ -283,48 +330,10 @@ benchmarks! { x25519_public_key.clone() ); - /// This is a randomly generated secret p256 ECDSA key - for mocking the provisioning certification - /// key - const PCK: [u8; 32] = [ - 117, 153, 212, 7, 220, 16, 181, 32, 110, 138, 4, 68, 208, 37, 104, 54, 1, 110, 232, 207, 100, - 168, 16, 99, 66, 83, 21, 178, 81, 155, 132, 37, - ]; - - let pck = tdx_quote::SigningKey::from_bytes(&PCK.into()).unwrap(); - let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key()).unwrap(); - let provisioning_certification_key = BoundedVec::try_from(pck_encoded.to_vec()).unwrap(); - - let quote = { - /// This is a randomly generated secret p256 ECDSA key - for mocking attestation - const ATTESTATION_KEY: [u8; 32] = [ - 167, 184, 203, 130, 240, 249, 191, 129, 206, 9, 200, 29, 99, 197, 64, 81, 135, 166, 59, 73, 31, - 27, 206, 207, 69, 248, 56, 195, 64, 92, 109, 46, - ]; - - let attestation_key = tdx_quote::SigningKey::from_bytes(&ATTESTATION_KEY.into()).unwrap(); - - let input_data = entropy_shared::QuoteInputData::new( - &threshold_account, - x25519_public_key, - nonce, - block_number, - ); - - tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0).as_bytes().to_vec() - }; - - let server_info = ServerInfo { - tss_account: threshold_account.clone(), - x25519_public_key, - endpoint: endpoint.clone(), - provisioning_certification_key, - }; - - // We need to tell the attestation handler that we want a quote. This will let the system to - // know to expect one back when we call `validate()`. - T::AttestationHandler::request_quote(&threshold_account, nonce); - - }: _(RawOrigin::Signed(bonder.clone()), ValidatorPrefs::default(), server_info, quote) + let block_number = 1; + let (quote, joining_server_info) = + prepare_attestation_for_validate::(threshold_account.clone(), x25519_public_key, endpoint.clone(), block_number); + }: _(RawOrigin::Signed(bonder.clone()), ValidatorPrefs::default(), joining_server_info, quote) verify { assert_last_event::( Event::::ValidatorCandidateAccepted( diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index cd583982d..fcd37e88a 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -41,6 +41,8 @@ use serde::{Deserialize, Serialize}; pub use crate::weights::WeightInfo; +pub mod pck; + #[cfg(test)] mod mock; @@ -68,6 +70,7 @@ pub mod pallet { DefaultNoBound, }; use frame_system::pallet_prelude::*; + use pck::PckCertChainVerifier; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha20Rng, ChaChaRng, @@ -94,6 +97,9 @@ pub mod pallet { /// The weight information of this pallet. type WeightInfo: WeightInfo; + /// A type that verifies a provisioning certification key (PCK) certificate chain. + type PckCertChainVerifier: PckCertChainVerifier; + /// Something that provides randomness in the runtime. type Randomness: Randomness>; @@ -124,6 +130,18 @@ pub mod pallet { pub endpoint: TssServerURL, pub provisioning_certification_key: VerifyingKey, } + + /// Information about a threshold server in the process of joining + /// This becomes a [ServerInfo] when the Pck certificate chain has been validated + #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + pub struct JoiningServerInfo { + pub tss_account: AccountId, + pub x25519_public_key: X25519PublicKey, + pub endpoint: TssServerURL, + pub pck_certificate_chain: Vec>, + } + /// Info that is requiered to do a proactive refresh #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, Default)] pub struct RefreshInfo { @@ -311,9 +329,24 @@ pub mod pallet { NoUnnominatingWhenSigner, NoUnnominatingWhenNextSigner, NoChangingThresholdAccountWhenSigner, + PckCertificateParse, + PckCertificateVerify, + PckCertificateBadPublicKey, + PckCertificateNoCertificate, FailedAttestationCheck, } + impl From for Error { + fn from(error: pck::PckParseVerifyError) -> Self { + match error { + pck::PckParseVerifyError::Parse => Error::::PckCertificateParse, + pck::PckParseVerifyError::Verify => Error::::PckCertificateVerify, + pck::PckParseVerifyError::BadPublicKey => Error::::PckCertificateBadPublicKey, + pck::PckParseVerifyError::NoCertificate => Error::::PckCertificateNoCertificate, + } + } + } + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -504,11 +537,26 @@ pub mod pallet { pub fn validate( origin: OriginFor, prefs: ValidatorPrefs, - server_info: ServerInfo, + joining_server_info: JoiningServerInfo, quote: Vec, ) -> DispatchResult { let who = ensure_signed(origin.clone())?; + let provisioning_certification_key = + T::PckCertChainVerifier::verify_pck_certificate_chain( + joining_server_info.pck_certificate_chain, + ) + .map_err(|error| { + let e: Error = error.into(); + e + })?; + + let server_info = ServerInfo:: { + tss_account: joining_server_info.tss_account, + x25519_public_key: joining_server_info.x25519_public_key, + endpoint: joining_server_info.endpoint, + provisioning_certification_key, + }; ensure!( server_info.endpoint.len() as u32 <= T::MaxEndpointLength::get(), Error::::EndpointTooLong diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 58898d137..6dea3171c 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -37,6 +37,7 @@ use sp_staking::{EraIndex, SessionIndex}; use sp_std::vec; use crate as pallet_staking_extension; +use pallet_staking_extension::pck::MockPckCertChainVerifier; type Block = frame_system::mocking::MockBlock; type BlockNumber = u64; @@ -419,6 +420,7 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = MockAttestationHandler; type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; + type PckCertChainVerifier = MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer b/pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer new file mode 100644 index 0000000000000000000000000000000000000000..768806c673709fb71537020abbabad2b1dc23773 GIT binary patch literal 659 zcmXqLV(K?&Vlr94%*4pVB%+kcb1iDFN$2P0EmMQ`2S{70v#l}UV&l+i^EhYA!pvll zVJKxF!NwfQ!ptM+nOBmUqY&&Kp%9dxU!vgb2$SQ2$vNj2733EsmSpDV8HyMPf%LKS za0e&ml_V-S=Oh*-8gd(OfM-Ch~kvMtrKjmv1bZ!Z(*nQf5%loS+O>FXz_7L{bCWhN(hi5#{Wtmy%j!APX{+k420HyOn$(X?{k= z|17|0XJi9Mo~$s4&tbp@q?i~PkrM{9Cxd|-lOn^@{ZBasf9gARD%>&aGSGW`QSWG5 z{WqmIDbv3SH3&@mV{?50sw!g B#yS81 literal 0 HcmV?d00001 diff --git a/pallets/staking/src/pck/mock.rs b/pallets/staking/src/pck/mock.rs new file mode 100644 index 000000000..5623cdd0d --- /dev/null +++ b/pallets/staking/src/pck/mock.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2023 Entropy Cryptography Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +use super::{CompressedVerifyingKey, PckCertChainVerifier, PckParseVerifyError}; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use rand::{rngs::StdRng, SeedableRng}; +use sp_std::vec::Vec; + +/// This is used in the benchmarking tests to check that ServerInfo is as expected +pub const MOCK_PCK_DERIVED_FROM_NULL_ARRAY: [u8; 33] = [ + 3, 237, 193, 27, 177, 204, 234, 67, 54, 141, 157, 13, 62, 87, 113, 224, 4, 121, 206, 251, 190, + 151, 134, 87, 68, 46, 37, 163, 127, 97, 252, 174, 108, +]; + +/// A PCK certificate chain verifier for testing. +/// Rather than actually use test certificates, we give here the TSS account ID instead of the first +/// certificate, and derive a keypair from it. The same keypair will be derived when creating a mock +/// quote in entropy-tss +pub struct MockPckCertChainVerifier {} + +impl PckCertChainVerifier for MockPckCertChainVerifier { + fn verify_pck_certificate_chain( + pck_certificate_chain: Vec>, + ) -> Result { + let first_certificate = + pck_certificate_chain.first().ok_or(PckParseVerifyError::NoCertificate)?; + + // Read the certificate bytes as a TSS account id + let tss_account_id: [u8; 32] = + first_certificate.clone().try_into().map_err(|_| PckParseVerifyError::Parse)?; + + // Derive a keypair + let pck_secret = signing_key_from_seed(tss_account_id); + + // Convert/compress the public key + let pck_public = VerifyingKey::from(&pck_secret); + let pck_public = pck_public.to_encoded_point(true).as_bytes().to_vec(); + pck_public.try_into().map_err(|_| PckParseVerifyError::Parse) + } +} + +pub fn signing_key_from_seed(input: [u8; 32]) -> SigningKey { + let mut pck_seeder = StdRng::from_seed(input); + SigningKey::random(&mut pck_seeder) +} diff --git a/pallets/staking/src/pck/mod.rs b/pallets/staking/src/pck/mod.rs new file mode 100644 index 000000000..3694d988a --- /dev/null +++ b/pallets/staking/src/pck/mod.rs @@ -0,0 +1,58 @@ +// Copyright (C) 2023 Entropy Cryptography Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +mod mock; +pub use mock::{signing_key_from_seed, MockPckCertChainVerifier, MOCK_PCK_DERIVED_FROM_NULL_ARRAY}; +mod production; +use super::VerifyingKey as CompressedVerifyingKey; +use core::array::TryFromSliceError; +use sp_std::vec::Vec; + +/// Provides a way of verifying a chain of certificates to give a chain of trust between the +/// provisioning certification key used to sign a TDX quote to the Intel route certificate authority +pub trait PckCertChainVerifier { + /// Verify an arbitrary chain of DER-encoded x509 certificates against Intel's root CA. + /// Typically this is two certificates, the PCK certificate and an intermediary provider + /// certificate + fn verify_pck_certificate_chain( + pck_certificate_chain: Vec>, + ) -> Result; +} + +/// An error when parsing or verifying a PCK or provider certificate +#[derive(Debug)] +pub enum PckParseVerifyError { + Parse, + Verify, + BadPublicKey, + NoCertificate, +} + +impl From for PckParseVerifyError { + fn from(_: spki::der::Error) -> PckParseVerifyError { + PckParseVerifyError::Parse + } +} + +impl From for PckParseVerifyError { + fn from(_: x509_verify::Error) -> PckParseVerifyError { + PckParseVerifyError::Verify + } +} + +impl From for PckParseVerifyError { + fn from(_: TryFromSliceError) -> PckParseVerifyError { + PckParseVerifyError::BadPublicKey + } +} diff --git a/pallets/staking/src/pck/production.rs b/pallets/staking/src/pck/production.rs new file mode 100644 index 000000000..7a0404123 --- /dev/null +++ b/pallets/staking/src/pck/production.rs @@ -0,0 +1,110 @@ +// Copyright (C) 2023 Entropy Cryptography Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +use sp_std::vec::Vec; +use x509_verify::{ + der::{Decode, Encode}, + x509_cert::Certificate, + Signature, VerifyInfo, VerifyingKey, +}; + +use super::{CompressedVerifyingKey, PckCertChainVerifier, PckParseVerifyError}; + +/// Intels root CA certificate in DER format available from here: +/// https://certificates.trustedservices.intel.com/Intel_SGX_Provisioning_Certification_RootCA.cer +/// Valid until December 31 2049 +const INTEL_ROOT_CA_DER: &[u8; 659] = + include_bytes!("Intel_SGX_Provisioning_Certification_RootCA.cer"); + +/// A PCK certificate chain verifier for use in production where entropy-tss is running on TDX +/// hardware and we have a PCK certificate chain +pub struct ProductionPckCertChainVerifier {} + +impl PckCertChainVerifier for ProductionPckCertChainVerifier { + fn verify_pck_certificate_chain( + pck_certificate_chain: Vec>, + ) -> Result { + let pck_uncompressed = verify_pck_cert_chain(pck_certificate_chain)?; + + // Compress / convert public key + let point = p256::EncodedPoint::from_bytes(pck_uncompressed) + .map_err(|_| PckParseVerifyError::BadPublicKey)?; + let pck_verifying_key = p256::ecdsa::VerifyingKey::from_encoded_point(&point) + .map_err(|_| PckParseVerifyError::BadPublicKey)?; + let pck_compressed = pck_verifying_key.to_encoded_point(true).as_bytes().to_vec(); + pck_compressed.try_into().map_err(|_| PckParseVerifyError::BadPublicKey) + } +} + +/// Validate PCK and provider certificates and if valid return the PCK +/// These certificates will be provided by a joining validator +fn verify_pck_cert_chain(certificates_der: Vec>) -> Result<[u8; 65], PckParseVerifyError> { + if certificates_der.is_empty() { + return Err(PckParseVerifyError::NoCertificate); + } + + // Parse the certificates + let mut certificates = Vec::new(); + for certificate in certificates_der { + certificates.push(Certificate::from_der(&certificate)?); + } + // Add the root certificate to the end of the chain. Since the root cert is self-signed, this + // will work regardless of whether the user has included this certicate in the chain or not + certificates.push(Certificate::from_der(INTEL_ROOT_CA_DER)?); + + // Verify the certificate chain + for i in 0..certificates.len() { + let verifying_key: &VerifyingKey = if i + 1 == certificates.len() { + &certificates[i].tbs_certificate.subject_public_key_info.clone().try_into()? + } else { + &certificates[i + 1].tbs_certificate.subject_public_key_info.clone().try_into()? + }; + verify_cert(&certificates[i], verifying_key)?; + } + + // Get the first certificate + let pck_key = &certificates + .first() + .ok_or(PckParseVerifyError::NoCertificate)? + .tbs_certificate + .subject_public_key_info + .subject_public_key; + + Ok(pck_key.as_bytes().ok_or(PckParseVerifyError::BadPublicKey)?.try_into()?) +} + +/// Given a cerificate and a public key, verify the certificate +fn verify_cert(subject: &Certificate, issuer_pk: &VerifyingKey) -> Result<(), PckParseVerifyError> { + let verify_info = VerifyInfo::new( + subject.tbs_certificate.to_der()?.into(), + Signature::new( + &subject.signature_algorithm, + subject.signature.as_bytes().ok_or(PckParseVerifyError::Parse)?, + ), + ); + Ok(issuer_pk.verify(&verify_info)?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verify_pck_cert_chain() { + let pck = include_bytes!("../../test_pck_certs/pck_cert.der").to_vec(); + let platform = include_bytes!("../../test_pck_certs/platform_pcs_cert.der").to_vec(); + assert!(ProductionPckCertChainVerifier::verify_pck_certificate_chain(vec![pck, platform]) + .is_ok()); + } +} diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index 98faa0d24..0340d4cf9 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -14,7 +14,8 @@ // along with this program. If not, see . use crate::{ - mock::*, tests::RuntimeEvent, Error, NextSignerInfo, NextSigners, ServerInfo, Signers, + mock::*, pck::MOCK_PCK_DERIVED_FROM_NULL_ARRAY, tests::RuntimeEvent, Error, JoiningServerInfo, + NextSignerInfo, NextSigners, ServerInfo, Signers, }; use codec::Encode; use frame_support::{assert_noop, assert_ok}; @@ -60,16 +61,16 @@ fn it_takes_in_an_endpoint() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -78,33 +79,33 @@ fn it_takes_in_an_endpoint() { assert_eq!(tss_account, 3); assert_eq!(Staking::threshold_to_stash(3).unwrap(), 1); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20; 26], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_noop!( Staking::validate( RuntimeOrigin::signed(4), pallet_staking::ValidatorPrefs::default(), - server_info, + joining_server_info, VALID_QUOTE.to_vec(), ), Error::::EndpointTooLong ); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20, 20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_noop!( Staking::validate( RuntimeOrigin::signed(4), pallet_staking::ValidatorPrefs::default(), - server_info, + joining_server_info, VALID_QUOTE.to_vec(), ), pallet_staking::Error::::NotController @@ -121,16 +122,16 @@ fn it_will_not_allow_validator_to_use_existing_tss_account() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -144,7 +145,7 @@ fn it_will_not_allow_validator_to_use_existing_tss_account() { Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), - server_info, + joining_server_info, VALID_QUOTE.to_vec(), ), Error::::TssAccountAlreadyExists @@ -161,16 +162,16 @@ fn it_changes_endpoint() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -193,16 +194,16 @@ fn it_changes_threshold_account() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -222,16 +223,16 @@ fn it_changes_threshold_account() { pallet_staking::RewardDestination::Account(2), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -257,16 +258,16 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), - server_info, + joining_server_info, VALID_QUOTE.to_vec(), )); @@ -277,16 +278,16 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { pallet_staking::RewardDestination::Account(2), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -309,16 +310,16 @@ fn it_deletes_when_no_bond_left() { pallet_staking::RewardDestination::Account(1), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::with_max_capacity(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); @@ -588,11 +589,11 @@ fn it_requires_attestation_before_validate_is_succesful() { pallet_staking::RewardDestination::Account(alice), )); - let server_info = ServerInfo { + let joining_server_info = JoiningServerInfo { tss_account: bob, x25519_public_key: NULL_ARR, endpoint: vec![20], - provisioning_certification_key: BoundedVec::try_from([0; 32].to_vec()).unwrap(), + pck_certificate_chain: vec![[0u8; 32].to_vec()], }; // First we test that an invalid attestation doesn't allow us to submit our candidacy. @@ -600,23 +601,32 @@ fn it_requires_attestation_before_validate_is_succesful() { Staking::validate( RuntimeOrigin::signed(alice), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), INVALID_QUOTE.to_vec(), ), Error::::FailedAttestationCheck ); assert_eq!(Staking::threshold_server(bob), None); - assert_eq!(Staking::threshold_to_stash(server_info.tss_account), None); + assert_eq!(Staking::threshold_to_stash(joining_server_info.tss_account), None); // Next we test that a valid attestation gets us into a candidate state. assert_ok!(Staking::validate( RuntimeOrigin::signed(alice), pallet_staking::ValidatorPrefs::default(), - server_info.clone(), + joining_server_info.clone(), VALID_QUOTE.to_vec(), )); + let server_info = ServerInfo:: { + tss_account: joining_server_info.tss_account, + x25519_public_key: joining_server_info.x25519_public_key, + endpoint: joining_server_info.endpoint, + provisioning_certification_key: MOCK_PCK_DERIVED_FROM_NULL_ARRAY + .to_vec() + .try_into() + .unwrap(), + }; assert_eq!(Staking::threshold_to_stash(bob), Some(alice)); assert_eq!(Staking::threshold_server(alice), Some(server_info)); }) diff --git a/pallets/staking/test_pck_certs/pck_cert.der b/pallets/staking/test_pck_certs/pck_cert.der new file mode 100644 index 0000000000000000000000000000000000000000..69279aeca337b2536d497de4fdc38585bde91867 GIT binary patch literal 1269 zcmXqLV)#WGZP~dlPJU0iU66pKbgx<%%5=Zk4CBS8ix=T11>fWtu~Kywk*s{ z1_g#n1`2G3{l0*gPoW!C;Lv903kSGTa6Hq&p!zRoW8f+jZ&TC|1 zU}<1(U|?iyY7`~TYix+b#p5F9)S{Biw9Mqhl2od?DAGWfjU5~!OpMT=VP<4!c4A;z zmfm4CcXrJ#@4(jZ|1Y;4sD1sca_!_C{e2&{dT*7V((v%W0r$5uTf7&l6wmi5eWX_u zedPTQhZU0lQ$H>${Q7sw;wEMugC=GU19@Oz$SSi)7>G59OwEryyI1<*O9#)BSH2jZ zV+`lM7h;eNQXtQgVvuBzupn+hOj}e&Nl8JmmA-yrL8e|wQE72WYD#fxQCVhkYO!7> zD5mt1^KfWtu~Kywk*s{ z1{sD@1`=${p)AZif}VLLsW}S4?hy(>`S~RZ&W&&c?n1sMH|Y~Vium7cJD1+O*yP+FDmRCJa*u6P_jD!& mHR-DgYW#CjYE3iu^@%Ljn3ODaIAZ4%_lAxymp0XWt^)wcYROCh literal 0 HcmV?d00001 diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b50d3dcaa..f801c6a94 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -718,6 +718,7 @@ impl pallet_staking_extension::Config for Runtime { type AttestationHandler = Attestation; type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; + type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_staking_extension::WeightInfo;