entropy_shared/
attestation.rs

1// Copyright (C) 2023 Entropy Cryptography Inc.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15//! TDX attestion related shared types and functions
16
17use crate::X25519PublicKey;
18use blake2::{Blake2b, Blake2b512, Digest};
19use codec::{Decode, Encode};
20
21/// The acceptable TDX measurement value for non-production chainspecs.
22/// This is the measurement given in mock quotes. Mock quotes have all zeros for each of the 5
23/// 48 bit measurement registers. The overall measurement is the Blake2b hash of these values.
24/// So this is the Blake2b hash of 5 * 48 zero bytes.
25pub const MEASUREMENT_VALUE_MOCK_QUOTE: [u8; 32] = [
26    91, 172, 96, 209, 130, 160, 167, 174, 152, 184, 193, 27, 88, 59, 117, 235, 74, 39, 194, 69,
27    147, 72, 129, 25, 224, 24, 189, 103, 224, 20, 107, 116,
28];
29
30/// Input data to be included in a TDX attestation
31pub struct QuoteInputData(pub [u8; 64]);
32
33impl QuoteInputData {
34    pub fn new<T: Encode>(
35        tss_account_id: T,
36        x25519_public_key: X25519PublicKey,
37        nonce: [u8; 32],
38        context: QuoteContext,
39    ) -> Self {
40        let mut hasher = Blake2b512::new();
41        hasher.update(tss_account_id.encode());
42        hasher.update(x25519_public_key);
43        hasher.update(nonce);
44        hasher.update(context.encode());
45        Self(hasher.finalize().into())
46    }
47}
48
49/// An indicator as to the context in which a quote is intended to be used
50#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
51#[non_exhaustive]
52pub enum QuoteContext {
53    /// To be used in the `validate` extrinsic
54    Validate,
55    /// To be used in the `change_endpoint` extrinsic
56    ChangeEndpoint,
57    /// To be used in the `change_threshold_accounts` extrinsic
58    ChangeThresholdAccounts,
59    /// To be used when requesting to recover an encryption key
60    EncryptionKeyRecoveryRequest,
61}
62
63#[cfg(any(feature = "std", feature = "wasm"))]
64impl std::fmt::Display for QuoteContext {
65    /// Custom display implementation so that it can be used to build a query string
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            QuoteContext::Validate => write!(f, "validate"),
69            QuoteContext::ChangeEndpoint => write!(f, "change_endpoint"),
70            QuoteContext::ChangeThresholdAccounts => write!(f, "change_threshold_accounts"),
71            QuoteContext::EncryptionKeyRecoveryRequest => {
72                write!(f, "encryption_key_recovery_request")
73            },
74        }
75    }
76}
77
78#[cfg(feature = "wasm-no-std")]
79use sp_std::vec::Vec;
80
81/// A trait for types which can handle attestation requests.
82#[cfg(not(feature = "wasm"))]
83pub trait AttestationHandler<AccountId> {
84    /// Verify that the given quote is valid and matches the given information about the attestee.
85    /// The Provisioning Certification Key (PCK) certifcate chain is extracted from the quote and
86    /// verified. If successful, the PCK public key used to sign the quote is returned.
87    fn verify_quote(
88        attestee: &AccountId,
89        x25519_public_key: X25519PublicKey,
90        quote: Vec<u8>,
91        context: QuoteContext,
92    ) -> Result<crate::BoundedVecEncodedVerifyingKey, VerifyQuoteError>;
93
94    /// Indicate to the attestation handler that a quote is desired.
95    ///
96    /// The `nonce` should be a piece of data (e.g a random number) which indicates that the quote
97    /// is reasonably fresh and has not been reused.
98    fn request_quote(attestee: &AccountId, nonce: [u8; 32]);
99}
100
101/// A convenience implementation for testing and benchmarking.
102#[cfg(not(feature = "wasm"))]
103impl<AccountId> AttestationHandler<AccountId> for () {
104    fn verify_quote(
105        _attestee: &AccountId,
106        _x25519_public_key: X25519PublicKey,
107        _quote: Vec<u8>,
108        _context: QuoteContext,
109    ) -> Result<crate::BoundedVecEncodedVerifyingKey, VerifyQuoteError> {
110        Ok(crate::BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap())
111    }
112
113    fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {}
114}
115
116/// An error when verifying a quote
117#[cfg(not(feature = "wasm"))]
118#[derive(Debug, Eq, PartialEq)]
119pub enum VerifyQuoteError {
120    /// Quote could not be parsed or verified
121    BadQuote,
122    /// Attestation extrinsic submitted when not requested
123    UnexpectedAttestation,
124    /// Hashed input data does not match what was expected
125    IncorrectInputData,
126    /// Unacceptable VM image running
127    BadMeasurementValue,
128    /// Cannot encode verifying key (PCK)
129    CannotEncodeVerifyingKey,
130    /// Cannot decode verifying key (PCK)
131    CannotDecodeVerifyingKey,
132    /// PCK certificate chain cannot be parsed
133    PckCertificateParse,
134    /// PCK certificate chain cannot be verified
135    PckCertificateVerify,
136    /// PCK certificate chain public key is not well formed
137    PckCertificateBadPublicKey,
138    /// Pck certificate could not be extracted from quote
139    PckCertificateNoCertificate,
140}
141
142#[cfg(feature = "std")]
143impl std::fmt::Display for VerifyQuoteError {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        match self {
146            VerifyQuoteError::BadQuote => write!(f, "Quote could not be parsed of verified"),
147            VerifyQuoteError::UnexpectedAttestation => {
148                write!(f, "Attestation extrinsic submitted when not requested")
149            },
150            VerifyQuoteError::IncorrectInputData => {
151                write!(f, "Hashed input data does not match what was expected")
152            },
153            VerifyQuoteError::BadMeasurementValue => write!(f, "Unacceptable VM image running"),
154            VerifyQuoteError::CannotEncodeVerifyingKey => {
155                write!(f, "Cannot encode verifying key (PCK)")
156            },
157            VerifyQuoteError::CannotDecodeVerifyingKey => {
158                write!(f, "Cannot decode verifying key (PCK)")
159            },
160            VerifyQuoteError::PckCertificateParse => {
161                write!(f, "PCK certificate chain cannot be parsed")
162            },
163            VerifyQuoteError::PckCertificateVerify => {
164                write!(f, "PCK certificate chain cannot be verified")
165            },
166            VerifyQuoteError::PckCertificateBadPublicKey => {
167                write!(f, "PCK certificate chain public key is not well formed")
168            },
169            VerifyQuoteError::PckCertificateNoCertificate => {
170                write!(f, "PCK certificate could not be extracted from quote")
171            },
172        }
173    }
174}
175
176#[cfg(feature = "std")]
177impl std::error::Error for VerifyQuoteError {}
178
179/// Verify a PCK certificate chain from a quote in production
180#[cfg(feature = "production")]
181pub fn verify_pck_certificate_chain(
182    quote: &tdx_quote::Quote,
183) -> Result<tdx_quote::VerifyingKey, VerifyQuoteError> {
184    quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify)
185}
186
187/// A mock version of verifying the PCK certificate chain.
188/// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
189/// so this function just decodes it, checks it was used to sign the quote, and returns it
190#[cfg(not(any(feature = "production", feature = "wasm")))]
191pub fn verify_pck_certificate_chain(
192    quote: &tdx_quote::Quote,
193) -> Result<tdx_quote::VerifyingKey, VerifyQuoteError> {
194    let provisioning_certification_key =
195        quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?;
196    let provisioning_certification_key = tdx_quote::decode_verifying_key(
197        &provisioning_certification_key
198            .try_into()
199            .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?,
200    )
201    .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?;
202
203    quote
204        .verify_with_pck(&provisioning_certification_key)
205        .map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
206    Ok(provisioning_certification_key)
207}
208
209/// Create a measurement value by hashing together all measurement registers from quote data
210pub fn compute_quote_measurement(quote: &tdx_quote::Quote) -> [u8; 32] {
211    let mut hasher = Blake2b::new();
212    hasher.update(quote.mrtd());
213    hasher.update(quote.rtmr0());
214    hasher.update(quote.rtmr1());
215    hasher.update(quote.rtmr2());
216    hasher.update(quote.rtmr3());
217    hasher.finalize().into()
218}