From d57afb1648e070f46e22c739d33705d11534210c Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Thu, 28 Oct 2021 12:09:35 +0100 Subject: [PATCH 1/9] Add ActivateCredential support for TKC This commit adds support for two operations, enabling key attestation via the ActivateCredential call. A test has also been added to verify attestation using the Endorsement Key. Signed-off-by: Ionut Mihalcea --- tss-esapi/src/abstraction/ek.rs | 8 +- .../abstraction/transient/key_attestation.rs | 203 ++++++++++++++++++ .../{transient.rs => transient/mod.rs} | 9 + .../transient_key_context_tests.rs | 87 +++++++- 4 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 tss-esapi/src/abstraction/transient/key_attestation.rs rename tss-esapi/src/abstraction/{transient.rs => transient/mod.rs} (99%) diff --git a/tss-esapi/src/abstraction/ek.rs b/tss-esapi/src/abstraction/ek.rs index e7a5b72e..91e240e5 100644 --- a/tss-esapi/src/abstraction/ek.rs +++ b/tss-esapi/src/abstraction/ek.rs @@ -24,9 +24,11 @@ use std::convert::TryFrom; const RSA_2048_EK_CERTIFICATE_NV_INDEX: u32 = 0x01c00002; const ECC_P256_EK_CERTIFICATE_NV_INDEX: u32 = 0x01c0000a; -// Source: TCG EK Credential Profile for TPM Family 2.0; Level 0 Version 2.3 Revision 2 -// Appendix B.3.3 and B.3.4 -fn create_ek_public_from_default_template( +/// Get the [`Public`] representing a default Endorsement Key +/// +/// Source: TCG EK Credential Profile for TPM Family 2.0; Level 0 Version 2.3 Revision 2 +/// Appendix B.3.3 and B.3.4 +pub fn create_ek_public_from_default_template( alg: AsymmetricAlgorithm, key_customization: IKC, ) -> Result { diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs new file mode 100644 index 00000000..5bf4163b --- /dev/null +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -0,0 +1,203 @@ +// Copyright 2021 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +use super::{ObjectWrapper, TransientKeyContext}; +use crate::{ + abstraction::ek, + constants::SessionType, + handles::{AuthHandle, SessionHandle}, + interface_types::{ + algorithm::{AsymmetricAlgorithm, HashingAlgorithm}, + session_handles::PolicySession, + }, + structures::{EncryptedSecret, IDObject, SymmetricDefinition}, + tss2_esys::{TPM2B_PUBLIC, TPMT_PUBLIC}, + utils::PublicKey, + Result, +}; +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug)] +/// Wrapper for the parameters needed by MakeCredential +pub struct MakeCredParams { + /// TPM name of the object + name: Vec, + /// Encoding of the public parameters of the object whose name + /// will be included in the credential computations + public: Vec, + /// Public part of the key used to protect the credential + attesting_key_pub: PublicKey, +} + +impl MakeCredParams { + pub fn name(&self) -> &[u8] { + &self.name + } + + pub fn public(&self) -> &[u8] { + &self.public + } + + pub fn attesting_key_pub(&self) -> &PublicKey { + &self.attesting_key_pub + } +} + +impl TransientKeyContext { + /// Get the data required to perform a MakeCredential + /// + /// # Parameters + /// + /// * `object` - the object whose TPM name will be included in + /// the credential + /// * `key` - the key to be used to encrypt the secret that wraps + /// the credential + /// + /// **Note**: If no `key` is given, the default Endorsement Key + /// will be used. + pub fn get_make_cred_params( + &mut self, + object: ObjectWrapper, + key: Option, + ) -> Result { + let object_handle = self.load_key(object.params, object.material, None)?; + let (object_public, object_name, _) = + self.context.read_public(object_handle).or_else(|e| { + self.context.flush_context(object_handle.into())?; + Err(e) + })?; + self.context.flush_context(object_handle.into())?; + + let public = TPM2B_PUBLIC::from(object_public); + let public = unsafe { + std::mem::transmute::()]>( + public.publicArea, + ) + }; + let attesting_key_pub = match key { + None => { + let key_handle = + ek::create_ek_object(&mut self.context, AsymmetricAlgorithm::Rsa, None)?; + let (attesting_key_pub, _, _) = + self.context.read_public(key_handle).or_else(|e| { + self.context.flush_context(key_handle.into())?; + Err(e) + })?; + self.context.flush_context(key_handle.into())?; + + attesting_key_pub.try_into()? + } + Some(key) => key.material.public, + }; + Ok(MakeCredParams { + name: object_name.value().to_vec(), + public: public.to_vec(), + attesting_key_pub, + }) + } + + /// Perform an ActivateCredential operation for the given object + /// + /// # Parameters + /// + /// * `object` - the object whose TPM name is included in the credential + /// * `key` - the key used to encrypt the secret that wraps the credential + /// * `credential_blob` - encrypted credential that will be returned by the + /// TPM + /// * `secret` - encrypted secret that was used to encrypt the credential + /// + /// **Note**: if no `key` is given, the default Endorsement Key + /// will be used. You can find more information about the default Endorsement + /// Key in the [ek] module. + pub fn activate_credential( + &mut self, + object: ObjectWrapper, + key: Option, + credential_blob: Vec, + secret: Vec, + ) -> Result> { + let credential_blob = IDObject::try_from(credential_blob)?; + let secret = EncryptedSecret::try_from(secret)?; + let object_handle = self.load_key(object.params, object.material, object.auth)?; + let session_2; + let key_handle = match key { + None => { + // No key was given, use the EK. This requires using a Policy session + session_2 = self + .context + .start_auth_session( + None, + None, + None, + SessionType::Policy, + SymmetricDefinition::AES_128_CFB, + HashingAlgorithm::Sha256, + ) + .or_else(|e| { + self.context.flush_context(object_handle.into())?; + Err(e) + })?; + let _ = self.context.policy_secret( + PolicySession::try_from(session_2.unwrap()) + .expect("Failed to convert auth session to policy session"), + AuthHandle::Endorsement, + Default::default(), + Default::default(), + Default::default(), + None, + ); + ek::create_ek_object(&mut self.context, AsymmetricAlgorithm::Rsa, None).or_else( + |e| { + self.context.flush_context(object_handle.into())?; + self.context + .flush_context(SessionHandle::from(session_2).into())?; + Err(e) + }, + )? + } + Some(key) => { + // Load key and create a HMAC session for it + session_2 = self + .context + .start_auth_session( + None, + None, + None, + SessionType::Hmac, + SymmetricDefinition::AES_128_CFB, + HashingAlgorithm::Sha256, + ) + .or_else(|e| { + self.context.flush_context(object_handle.into())?; + Err(e) + })?; + self.load_key(key.params, key.material, key.auth) + .or_else(|e| { + self.context.flush_context(object_handle.into())?; + self.context + .flush_context(SessionHandle::from(session_2).into())?; + Err(e) + })? + } + }; + + let (session_1, _, _) = self.context.sessions(); + let credential = self + .context + .execute_with_sessions((session_1, session_2, None), |ctx| { + ctx.activate_credential(object_handle, key_handle, credential_blob, secret) + }) + .or_else(|e| { + self.context.flush_context(object_handle.into())?; + self.context.flush_context(key_handle.into())?; + self.context + .flush_context(SessionHandle::from(session_2).into())?; + Err(e) + })?; + + self.context.flush_context(object_handle.into())?; + self.context.flush_context(key_handle.into())?; + self.context + .flush_context(SessionHandle::from(session_2).into())?; + Ok(credential.value().to_vec()) + } +} diff --git a/tss-esapi/src/abstraction/transient.rs b/tss-esapi/src/abstraction/transient/mod.rs similarity index 99% rename from tss-esapi/src/abstraction/transient.rs rename to tss-esapi/src/abstraction/transient/mod.rs index 44014c9d..0e52e87b 100644 --- a/tss-esapi/src/abstraction/transient.rs +++ b/tss-esapi/src/abstraction/transient/mod.rs @@ -39,6 +39,8 @@ use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use zeroize::Zeroize; +mod key_attestation; + /// Parameters for the kinds of keys supported by the context #[derive(Debug, Clone, Copy)] pub enum KeyParams { @@ -89,6 +91,13 @@ impl KeyMaterial { } } +#[derive(Debug, Clone)] +pub struct ObjectWrapper { + pub material: KeyMaterial, + pub params: KeyParams, + pub auth: Option, +} + /// Structure offering an abstracted programming experience. /// /// The `TransientKeyContext` makes use of a root key from which the other, client-controlled diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index a360222e..887926bb 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -1,11 +1,14 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use tss_esapi::{ - abstraction::transient::{KeyParams, TransientKeyContextBuilder}, + abstraction::ek, + abstraction::transient::{KeyParams, ObjectWrapper, TransientKeyContextBuilder}, constants::response_code::Tss2ResponseCodeKind, interface_types::{ - algorithm::{EccSchemeAlgorithm, HashingAlgorithm, RsaSchemeAlgorithm}, + algorithm::{ + AsymmetricAlgorithm, EccSchemeAlgorithm, HashingAlgorithm, RsaSchemeAlgorithm, + }, ecc::EccCurve, key_bits::RsaKeyBits, resource_handles::Hierarchy, @@ -597,3 +600,81 @@ fn ctx_migration_test() { panic!("Got wrong type of key from TPM"); } } + +#[test] +fn activate_credential() { + // create a Transient key context, generate a key and + // obtain the Make Credential parameters + let mut ctx = create_ctx(); + let params = KeyParams::Ecc { + curve: EccCurve::NistP256, + scheme: EccScheme::create( + EccSchemeAlgorithm::EcDsa, + Some(HashingAlgorithm::Sha256), + None, + ) + .expect("Failed to create ecc scheme"), + }; + let (material, auth) = ctx.create_key(params, 16).unwrap(); + let obj = ObjectWrapper { + material, + auth, + params, + }; + let make_cred_params = ctx.get_make_cred_params(obj.clone(), None).unwrap(); + + drop(ctx); + + // create a normal Context and make the credential + let mut basic_ctx = crate::common::create_ctx_with_session(); + + // the public part of the EK is used, so we retrieve the parameters + let key_pub = + ek::create_ek_public_from_default_template(AsymmetricAlgorithm::Rsa, None).unwrap(); + let key_pub = if let Public::Rsa { + object_attributes, + name_hashing_algorithm, + auth_policy, + parameters, + .. + } = key_pub + { + Public::Rsa { + object_attributes, + name_hashing_algorithm, + auth_policy, + parameters, + unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub().clone() { + PublicKeyRsa::try_from(val).unwrap() + } else { + panic!("Wrong public key type"); + }, + } + } else { + panic!("Wrong Public type"); + }; + let pub_handle = basic_ctx + .load_external_public(&key_pub, Hierarchy::Owner) + .unwrap(); + + // Credential to expect back as proof for attestation + let credential = vec![0x53; 16]; + + let (cred, secret) = basic_ctx + .make_credential( + pub_handle, + credential.clone().try_into().unwrap(), + make_cred_params.name().to_vec().try_into().unwrap(), + ) + .unwrap(); + + drop(basic_ctx); + + // Create a new Transient key context and activate the credential + let mut ctx = create_ctx(); + let cred_back = ctx + .activate_credential(obj, None, cred.value().to_vec(), secret.value().to_vec()) + .unwrap(); + + assert_eq!(cred_back, credential); +} From 84a79ae2d7e092bc42a3fde7a5b53116fc81d4fe Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Tue, 2 Nov 2021 10:27:18 +0000 Subject: [PATCH 2/9] Refactor attesting key setup Split up the setup for the attesting key into different methods so it's clearer what is being done. Signed-off-by: Ionut Mihalcea --- .../abstraction/transient/key_attestation.rs | 128 +++++++++--------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs index 5bf4163b..dd5cfd45 100644 --- a/tss-esapi/src/abstraction/transient/key_attestation.rs +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -4,10 +4,10 @@ use super::{ObjectWrapper, TransientKeyContext}; use crate::{ abstraction::ek, constants::SessionType, - handles::{AuthHandle, SessionHandle}, + handles::{AuthHandle, KeyHandle, SessionHandle}, interface_types::{ algorithm::{AsymmetricAlgorithm, HashingAlgorithm}, - session_handles::PolicySession, + session_handles::{AuthSession, PolicySession}, }, structures::{EncryptedSecret, IDObject, SymmetricDefinition}, tss2_esys::{TPM2B_PUBLIC, TPMT_PUBLIC}, @@ -118,67 +118,14 @@ impl TransientKeyContext { let credential_blob = IDObject::try_from(credential_blob)?; let secret = EncryptedSecret::try_from(secret)?; let object_handle = self.load_key(object.params, object.material, object.auth)?; - let session_2; - let key_handle = match key { - None => { - // No key was given, use the EK. This requires using a Policy session - session_2 = self - .context - .start_auth_session( - None, - None, - None, - SessionType::Policy, - SymmetricDefinition::AES_128_CFB, - HashingAlgorithm::Sha256, - ) - .or_else(|e| { - self.context.flush_context(object_handle.into())?; - Err(e) - })?; - let _ = self.context.policy_secret( - PolicySession::try_from(session_2.unwrap()) - .expect("Failed to convert auth session to policy session"), - AuthHandle::Endorsement, - Default::default(), - Default::default(), - Default::default(), - None, - ); - ek::create_ek_object(&mut self.context, AsymmetricAlgorithm::Rsa, None).or_else( - |e| { - self.context.flush_context(object_handle.into())?; - self.context - .flush_context(SessionHandle::from(session_2).into())?; - Err(e) - }, - )? - } - Some(key) => { - // Load key and create a HMAC session for it - session_2 = self - .context - .start_auth_session( - None, - None, - None, - SessionType::Hmac, - SymmetricDefinition::AES_128_CFB, - HashingAlgorithm::Sha256, - ) - .or_else(|e| { - self.context.flush_context(object_handle.into())?; - Err(e) - })?; - self.load_key(key.params, key.material, key.auth) - .or_else(|e| { - self.context.flush_context(object_handle.into())?; - self.context - .flush_context(SessionHandle::from(session_2).into())?; - Err(e) - })? - } - }; + let (key_handle, session_2) = match key { + Some(key) => self.prepare_key_activate_cred(key), + None => self.prepare_ek_activate_cred(), + } + .or_else(|e| { + self.context.flush_context(object_handle.into())?; + Err(e) + })?; let (session_1, _, _) = self.context.sessions(); let credential = self @@ -200,4 +147,59 @@ impl TransientKeyContext { .flush_context(SessionHandle::from(session_2).into())?; Ok(credential.value().to_vec()) } + + // No key was given, use the EK. This requires using a Policy session + fn prepare_ek_activate_cred(&mut self) -> Result<(KeyHandle, Option)> { + let session = self.context.start_auth_session( + None, + None, + None, + SessionType::Policy, + SymmetricDefinition::AES_128_CFB, + HashingAlgorithm::Sha256, + )?; + let _ = self.context.policy_secret( + PolicySession::try_from(session.unwrap()) + .expect("Failed to convert auth session to policy session"), + AuthHandle::Endorsement, + Default::default(), + Default::default(), + Default::default(), + None, + ); + Ok(( + ek::create_ek_object(&mut self.context, AsymmetricAlgorithm::Rsa, None).or_else( + |e| { + self.context + .flush_context(SessionHandle::from(session).into())?; + Err(e) + }, + )?, + session, + )) + } + + // Load key and create a HMAC session for it + fn prepare_key_activate_cred( + &mut self, + key: ObjectWrapper, + ) -> Result<(KeyHandle, Option)> { + let session = self.context.start_auth_session( + None, + None, + None, + SessionType::Hmac, + SymmetricDefinition::AES_128_CFB, + HashingAlgorithm::Sha256, + )?; + Ok(( + self.load_key(key.params, key.material, key.auth) + .or_else(|e| { + self.context + .flush_context(SessionHandle::from(session).into())?; + Err(e) + })?, + session, + )) + } } From 9d11249db8f2e6c8077afecbbf9b5fb157552fd6 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Tue, 2 Nov 2021 10:28:53 +0000 Subject: [PATCH 3/9] Allow the setting more hierarchy auths in TKC This commit expands the scope of the TransientKeyContextBuilder to adding more than one hierarchy auth value. This is needed for attesting keys with the default Endorsement Key which uses the Endorsement Hierarchy for authorization by default. Signed-off-by: Ionut Mihalcea --- tss-esapi/src/abstraction/transient/mod.rs | 35 ++++++++++--------- .../src/interface_types/resource_handles.rs | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tss-esapi/src/abstraction/transient/mod.rs b/tss-esapi/src/abstraction/transient/mod.rs index 0e52e87b..fe11e173 100644 --- a/tss-esapi/src/abstraction/transient/mod.rs +++ b/tss-esapi/src/abstraction/transient/mod.rs @@ -36,6 +36,7 @@ use crate::{ use log::error; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use zeroize::Zeroize; @@ -532,10 +533,10 @@ impl TransientKeyContext { #[derive(Debug)] pub struct TransientKeyContextBuilder { tcti_name_conf: TctiNameConf, - hierarchy: Hierarchy, root_key_size: u16, // TODO: replace with root key PUBLIC definition root_key_auth_size: usize, - hierarchy_auth: Vec, + root_hierarchy: Hierarchy, + hierarchy_auth: HashMap>, default_context_cipher: SymmetricDefinitionObject, session_hash_alg: HashingAlgorithm, } @@ -545,10 +546,10 @@ impl TransientKeyContextBuilder { pub fn new() -> Self { TransientKeyContextBuilder { tcti_name_conf: TctiNameConf::Device(Default::default()), - hierarchy: Hierarchy::Owner, + root_hierarchy: Hierarchy::Owner, root_key_size: 2048, root_key_auth_size: 32, - hierarchy_auth: Vec::new(), + hierarchy_auth: HashMap::new(), default_context_cipher: SymmetricDefinitionObject::AES_256_CFB, session_hash_alg: HashingAlgorithm::Sha256, } @@ -560,9 +561,15 @@ impl TransientKeyContextBuilder { self } + /// Set the auth values for any hierarchies that will be used + pub fn with_hierarchy_auth(mut self, hierarchy: Hierarchy, auth: Vec) -> Self { + let _ = self.hierarchy_auth.insert(hierarchy, auth); + self + } + /// Define which hierarchy will be used for the keys being managed. - pub fn with_hierarchy(mut self, hierarchy: Hierarchy) -> Self { - self.hierarchy = hierarchy; + pub fn with_root_hierarchy(mut self, hierarchy: Hierarchy) -> Self { + self.root_hierarchy = hierarchy; self } @@ -578,12 +585,6 @@ impl TransientKeyContextBuilder { self } - /// Input the authentication value of the working hierarchy. - pub fn with_hierarchy_auth(mut self, hierarchy_auth: Vec) -> Self { - self.hierarchy_auth = hierarchy_auth; - self - } - /// Define the cipher to be used within this context as a default. /// /// Currently this default is used for: @@ -624,7 +625,7 @@ impl TransientKeyContextBuilder { /// `Context::set_handle_auth` /// * if the root key authentication size is given greater than 32 or if the root key size is /// not 1024, 2048, 3072 or 4096, a `InvalidParam` wrapper error is returned - pub fn build(self) -> Result { + pub fn build(mut self) -> Result { if self.root_key_auth_size > 32 { return Err(Error::local_error(ErrorKind::WrongParamSize)); } @@ -640,9 +641,9 @@ impl TransientKeyContextBuilder { None }; - if !self.hierarchy_auth.is_empty() { - let auth_hierarchy = Auth::try_from(self.hierarchy_auth)?; - context.tr_set_auth(self.hierarchy.into(), &auth_hierarchy)?; + for (hierarchy, auth) in self.hierarchy_auth.drain() { + let auth_hierarchy = Auth::try_from(auth)?; + context.tr_set_auth(hierarchy.into(), &auth_hierarchy)?; } let session = context @@ -669,7 +670,7 @@ impl TransientKeyContextBuilder { let root_key_handle = context .create_primary( - self.hierarchy, + self.root_hierarchy, &create_restricted_decryption_rsa_public( self.default_context_cipher, root_key_rsa_key_bits, diff --git a/tss-esapi/src/interface_types/resource_handles.rs b/tss-esapi/src/interface_types/resource_handles.rs index b42b9622..45c31363 100644 --- a/tss-esapi/src/interface_types/resource_handles.rs +++ b/tss-esapi/src/interface_types/resource_handles.rs @@ -13,7 +13,7 @@ use std::convert::TryFrom; /// /// Enum describing the object hierarchies in a TPM 2.0. ////////////////////////////////////////////////////////////////////////////////// -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Hierarchy { Owner, Platform, From 1f170fcb914ca298d8b3cfa311faff2430ad9d49 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Tue, 9 Nov 2021 15:13:38 +0000 Subject: [PATCH 4/9] A few fixes * Changed the serialisation of the TPM2B_Public to use the marshaling command * Changed the MakeCredParams struct to be fully public to avoid needless clones * Factored out the code for obtaining the EK public key Signed-off-by: Ionut Mihalcea --- .../abstraction/transient/key_attestation.rs | 70 +++++++++---------- tss-esapi/src/abstraction/transient/mod.rs | 2 + .../transient_key_context_tests.rs | 4 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs index dd5cfd45..c70e9491 100644 --- a/tss-esapi/src/abstraction/transient/key_attestation.rs +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -10,36 +10,23 @@ use crate::{ session_handles::{AuthSession, PolicySession}, }, structures::{EncryptedSecret, IDObject, SymmetricDefinition}, - tss2_esys::{TPM2B_PUBLIC, TPMT_PUBLIC}, + tss2_esys::{Tss2_MU_TPM2B_PUBLIC_Marshal, TPM2B_PUBLIC}, utils::PublicKey, - Result, + Error, Result, }; -use std::convert::{TryFrom, TryInto}; +use log::error; +use std::convert::TryFrom; #[derive(Debug)] /// Wrapper for the parameters needed by MakeCredential pub struct MakeCredParams { /// TPM name of the object - name: Vec, + pub name: Vec, /// Encoding of the public parameters of the object whose name /// will be included in the credential computations - public: Vec, + pub public: Vec, /// Public part of the key used to protect the credential - attesting_key_pub: PublicKey, -} - -impl MakeCredParams { - pub fn name(&self) -> &[u8] { - &self.name - } - - pub fn public(&self) -> &[u8] { - &self.public - } - - pub fn attesting_key_pub(&self) -> &PublicKey { - &self.attesting_key_pub - } + pub attesting_key_pub: PublicKey, } impl TransientKeyContext { @@ -68,29 +55,29 @@ impl TransientKeyContext { self.context.flush_context(object_handle.into())?; let public = TPM2B_PUBLIC::from(object_public); - let public = unsafe { - std::mem::transmute::()]>( - public.publicArea, + let mut pub_buf = [0u8; std::mem::size_of::()]; + let mut offset = 0; + let result = unsafe { + Tss2_MU_TPM2B_PUBLIC_Marshal( + &public, + &mut pub_buf as *mut u8, + pub_buf.len() as u64, + &mut offset, ) }; - let attesting_key_pub = match key { - None => { - let key_handle = - ek::create_ek_object(&mut self.context, AsymmetricAlgorithm::Rsa, None)?; - let (attesting_key_pub, _, _) = - self.context.read_public(key_handle).or_else(|e| { - self.context.flush_context(key_handle.into())?; - Err(e) - })?; - self.context.flush_context(key_handle.into())?; + let result = Error::from_tss_rc(result); + if !result.is_success() { + error!("Error in marshalling TPM2B"); + return Err(result); + } - attesting_key_pub.try_into()? - } + let attesting_key_pub = match key { + None => get_ek_object_public(&mut self.context)?, Some(key) => key.material.public, }; Ok(MakeCredParams { name: object_name.value().to_vec(), - public: public.to_vec(), + public: pub_buf.to_vec(), attesting_key_pub, }) } @@ -203,3 +190,14 @@ impl TransientKeyContext { )) } } + +fn get_ek_object_public(context: &mut crate::Context) -> Result { + let key_handle = ek::create_ek_object(context, AsymmetricAlgorithm::Rsa, None)?; + let (attesting_key_pub, _, _) = context.read_public(key_handle).or_else(|e| { + context.flush_context(key_handle.into())?; + Err(e) + })?; + context.flush_context(key_handle.into())?; + + PublicKey::try_from(attesting_key_pub) +} diff --git a/tss-esapi/src/abstraction/transient/mod.rs b/tss-esapi/src/abstraction/transient/mod.rs index fe11e173..0e8071db 100644 --- a/tss-esapi/src/abstraction/transient/mod.rs +++ b/tss-esapi/src/abstraction/transient/mod.rs @@ -42,6 +42,8 @@ use zeroize::Zeroize; mod key_attestation; +pub use key_attestation::MakeCredParams; + /// Parameters for the kinds of keys supported by the context #[derive(Debug, Clone, Copy)] pub enum KeyParams { diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index 887926bb..67244d05 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -644,7 +644,7 @@ fn activate_credential() { name_hashing_algorithm, auth_policy, parameters, - unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub().clone() { + unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub { PublicKeyRsa::try_from(val).unwrap() } else { panic!("Wrong public key type"); @@ -664,7 +664,7 @@ fn activate_credential() { .make_credential( pub_handle, credential.clone().try_into().unwrap(), - make_cred_params.name().to_vec().try_into().unwrap(), + make_cred_params.name.try_into().unwrap(), ) .unwrap(); From 41a3f9df8bccfba56dd74d1d1547a211431b43b8 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Mon, 15 Nov 2021 10:44:57 +0000 Subject: [PATCH 5/9] Return marshaled TPMT_PUBLIC instead Instead of serializing TPM2B_PUBLIC, use the embedded TPMT_PUBLIC instead, as this is what the key name is computed over. Signed-off-by: Ionut Mihalcea --- .../src/abstraction/transient/key_attestation.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs index c70e9491..c2313bfa 100644 --- a/tss-esapi/src/abstraction/transient/key_attestation.rs +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -10,7 +10,7 @@ use crate::{ session_handles::{AuthSession, PolicySession}, }, structures::{EncryptedSecret, IDObject, SymmetricDefinition}, - tss2_esys::{Tss2_MU_TPM2B_PUBLIC_Marshal, TPM2B_PUBLIC}, + tss2_esys::{Tss2_MU_TPMT_PUBLIC_Marshal, TPM2B_PUBLIC, TPMT_PUBLIC}, utils::PublicKey, Error, Result, }; @@ -54,12 +54,13 @@ impl TransientKeyContext { })?; self.context.flush_context(object_handle.into())?; + // Name of objects is derived from their publicArea, i.e. the marshaled TPMT_PUBLIC let public = TPM2B_PUBLIC::from(object_public); - let mut pub_buf = [0u8; std::mem::size_of::()]; + let mut pub_buf = [0u8; std::mem::size_of::()]; let mut offset = 0; let result = unsafe { - Tss2_MU_TPM2B_PUBLIC_Marshal( - &public, + Tss2_MU_TPMT_PUBLIC_Marshal( + &public.publicArea, &mut pub_buf as *mut u8, pub_buf.len() as u64, &mut offset, @@ -70,6 +71,8 @@ impl TransientKeyContext { error!("Error in marshalling TPM2B"); return Err(result); } + // `offset` will be small, so no risk in the conversion below + let public = pub_buf[..offset as usize].to_vec(); let attesting_key_pub = match key { None => get_ek_object_public(&mut self.context)?, @@ -77,7 +80,7 @@ impl TransientKeyContext { }; Ok(MakeCredParams { name: object_name.value().to_vec(), - public: pub_buf.to_vec(), + public, attesting_key_pub, }) } From ec43a0e9948756e8089e3af1261b4e9aa5f09123 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Mon, 15 Nov 2021 10:47:23 +0000 Subject: [PATCH 6/9] Add more tests Adding more tests for the key attestation operations. Signed-off-by: Ionut Mihalcea --- tss-esapi/Cargo.toml | 1 + .../transient_key_context_tests.rs | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/tss-esapi/Cargo.toml b/tss-esapi/Cargo.toml index 3bb27d9b..fbb4b6c1 100644 --- a/tss-esapi/Cargo.toml +++ b/tss-esapi/Cargo.toml @@ -27,6 +27,7 @@ primal = "0.3.0" [dev-dependencies] env_logger = "0.7.1" +sha2 = "0.9.8" [features] generate-bindings = ["tss-esapi-sys/generate-bindings"] diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index 67244d05..142f136a 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -678,3 +678,154 @@ fn activate_credential() { assert_eq!(cred_back, credential); } + +#[test] +fn make_cred_params_name() { + // create a Transient key context, generate a key and + // obtain the Make Credential parameters + let mut ctx = create_ctx(); + let params = KeyParams::Ecc { + curve: EccCurve::NistP256, + scheme: EccScheme::create( + EccSchemeAlgorithm::EcDsa, + Some(HashingAlgorithm::Sha256), + None, + ) + .expect("Failed to create ecc scheme"), + }; + let (material, auth) = ctx.create_key(params, 16).unwrap(); + let obj = ObjectWrapper { + material, + auth, + params, + }; + let make_cred_params = ctx.get_make_cred_params(obj, None).unwrap(); + + // Verify that the name provided in the parameters is + // consistent with the public buffer + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(make_cred_params.public); + let hash = hasher.finalize(); + // The first 2 bytes of the name represent the hash algorithm used + assert_eq!(make_cred_params.name[2..], hash[..]); +} + +#[test] +fn activate_credential_wrong_key() { + // create a Transient key context, generate two keys and + // obtain the Make Credential parameters for the first one + let mut ctx = create_ctx(); + let params = KeyParams::Ecc { + curve: EccCurve::NistP256, + scheme: EccScheme::create( + EccSchemeAlgorithm::EcDsa, + Some(HashingAlgorithm::Sha256), + None, + ) + .expect("Failed to create ecc scheme"), + }; + // "Good" key (for which the credential will be generated) + let (material, auth) = ctx.create_key(params, 16).unwrap(); + let obj = ObjectWrapper { + material, + auth, + params, + }; + let make_cred_params = ctx.get_make_cred_params(obj, None).unwrap(); + + // "Wrong" key (which will be used instead of the good key in attestation) + let (material, auth) = ctx.create_key(params, 16).unwrap(); + let wrong_obj = ObjectWrapper { + material, + auth, + params, + }; + + drop(ctx); + + // create a normal Context and make the credential + let mut basic_ctx = crate::common::create_ctx_with_session(); + + // the public part of the EK is used, so we retrieve the parameters + let key_pub = + ek::create_ek_public_from_default_template(AsymmetricAlgorithm::Rsa, None).unwrap(); + let key_pub = if let Public::Rsa { + object_attributes, + name_hashing_algorithm, + auth_policy, + parameters, + .. + } = key_pub + { + Public::Rsa { + object_attributes, + name_hashing_algorithm, + auth_policy, + parameters, + unique: if let PublicKey::Rsa(val) = make_cred_params.attesting_key_pub { + PublicKeyRsa::try_from(val).unwrap() + } else { + panic!("Wrong public key type"); + }, + } + } else { + panic!("Wrong Public type"); + }; + let pub_handle = basic_ctx + .load_external_public(&key_pub, Hierarchy::Owner) + .unwrap(); + + // Credential to expect back as proof for attestation + let credential = vec![0x53; 16]; + + let (cred, secret) = basic_ctx + .make_credential( + pub_handle, + credential.try_into().unwrap(), + make_cred_params.name.try_into().unwrap(), + ) + .unwrap(); + + drop(basic_ctx); + + // Create a new Transient key context and activate the credential + let mut ctx = create_ctx(); + let _ = ctx + .activate_credential( + wrong_obj, + None, + cred.value().to_vec(), + secret.value().to_vec(), + ) + .unwrap_err(); +} + +#[test] +fn activate_credential_wrong_data() { + let mut ctx = create_ctx(); + let params = KeyParams::Ecc { + curve: EccCurve::NistP256, + scheme: EccScheme::create( + EccSchemeAlgorithm::EcDsa, + Some(HashingAlgorithm::Sha256), + None, + ) + .expect("Failed to create ecc scheme"), + }; + // "Good" key (for which the credential will be generated) + let (material, auth) = ctx.create_key(params, 16).unwrap(); + let obj = ObjectWrapper { + material, + auth, + params, + }; + + let _ = ctx + .activate_credential(obj.clone(), None, vec![], vec![]) + .unwrap_err(); + + let _ = ctx + .activate_credential(obj, None, vec![0xaa; 52], vec![0x55; 256]) + .unwrap_err(); +} From d574d3cb9b0f6ed9fb6bffdebeb28fea06553917 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Mon, 15 Nov 2021 15:59:42 +0000 Subject: [PATCH 7/9] Fix usize conversion This commit fixes the conversion from `TPMT_PUBLIC` size to the input of Tss2_MU_TPMT_PUBLIC_Marshal. A new WrapperErrorKind variant is added for internal errors which were not expected (essentially as a replacement for `unwrap`). The cross-compilation script was also moved to run per-commit in the CI instead of nightly - the fix above would've been obvious if we tried cross-compilation. Signed-off-by: Ionut Mihalcea --- .github/workflows/ci.yml | 2 ++ .github/workflows/nightly.yml | 2 -- tss-esapi/src/abstraction/transient/key_attestation.rs | 8 +++++--- tss-esapi/src/error.rs | 7 ++++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c6e8f5a..ec49adbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: run: docker build -t ubuntucontainer tss-esapi/tests/ --file tss-esapi/tests/Dockerfile-ubuntu - name: Run the container run: docker run -v $(pwd):/tmp/rust-tss-esapi -w /tmp/rust-tss-esapi/tss-esapi ubuntucontainer /tmp/rust-tss-esapi/tss-esapi/tests/all-ubuntu.sh + - name: Run the cross-compilation script + run: docker run -v $(pwd):/tmp/rust-tss-esapi -w /tmp/rust-tss-esapi/tss-esapi ubuntucontainer /tmp/rust-tss-esapi/tss-esapi/tests/cross-compile.sh tests-ubuntu-v3: name: Ubuntu tests on v3.x.y of tpm2-tss diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a7344e7c..24f09dfb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -51,5 +51,3 @@ jobs: run: docker run -v $(pwd):/tmp/rust-tss-esapi -w /tmp/rust-tss-esapi/tss-esapi --security-opt seccomp=unconfined ubuntucontainer /tmp/rust-tss-esapi/tss-esapi/tests/coverage.sh - name: Collect coverage results run: bash <(curl -s https://codecov.io/bash) - - name: Run the cross-compilation script - run: docker run -v $(pwd):/tmp/rust-tss-esapi -w /tmp/rust-tss-esapi/tss-esapi ubuntucontainer /tmp/rust-tss-esapi/tss-esapi/tests/cross-compile.sh diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs index c2313bfa..6d8e13cd 100644 --- a/tss-esapi/src/abstraction/transient/key_attestation.rs +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -12,10 +12,10 @@ use crate::{ structures::{EncryptedSecret, IDObject, SymmetricDefinition}, tss2_esys::{Tss2_MU_TPMT_PUBLIC_Marshal, TPM2B_PUBLIC, TPMT_PUBLIC}, utils::PublicKey, - Error, Result, + Error, Result, WrapperErrorKind, }; use log::error; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; #[derive(Debug)] /// Wrapper for the parameters needed by MakeCredential @@ -62,7 +62,9 @@ impl TransientKeyContext { Tss2_MU_TPMT_PUBLIC_Marshal( &public.publicArea, &mut pub_buf as *mut u8, - pub_buf.len() as u64, + std::mem::size_of::() + .try_into() + .map_err(|_| Error::local_error(WrapperErrorKind::InternalError))?, &mut offset, ) }; diff --git a/tss-esapi/src/error.rs b/tss-esapi/src/error.rs index d7c28678..00d5c1e0 100644 --- a/tss-esapi/src/error.rs +++ b/tss-esapi/src/error.rs @@ -62,7 +62,7 @@ pub enum WrapperErrorKind { InconsistentParams, /// Returned when the value of a parameter is not yet supported. UnsupportedParam, - /// Returend when the value of a parameter is invalid for that type. + /// Returned when the value of a parameter is invalid for that type. InvalidParam, /// Returned when the TPM returns an invalid value from a call. WrongValueFromTpm, @@ -72,6 +72,8 @@ pub enum WrapperErrorKind { /// Returned when a handle is required to be in a specific state /// (i.g. Open, Flushed, Closed) but it is not. InvalidHandleState, + /// An unexpected internal error occurred. + InternalError, } impl std::fmt::Display for WrapperErrorKind { @@ -97,6 +99,9 @@ impl std::fmt::Display for WrapperErrorKind { WrapperErrorKind::WrongValueFromTpm => write!(f, "the TPM returned an invalid value."), WrapperErrorKind::MissingAuthSession => write!(f, "Missing authorization session"), WrapperErrorKind::InvalidHandleState => write!(f, "Invalid handle state"), + WrapperErrorKind::InternalError => { + write!(f, "an unexpected error occurred within the crate") + } } } } From c01cf01162a3d78957e81159bce96038cf25bc9e Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Wed, 17 Nov 2021 11:17:12 +0000 Subject: [PATCH 8/9] PR feedback Improved the tests to check for the exact response code expected. Improved the documentation on `MakeCredentialParams` to detail what the contents are meant for. Signed-off-by: Ionut Mihalcea --- .../abstraction/transient/key_attestation.rs | 16 +++++++++++- .../transient_key_context_tests.rs | 25 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/tss-esapi/src/abstraction/transient/key_attestation.rs b/tss-esapi/src/abstraction/transient/key_attestation.rs index 6d8e13cd..625eac32 100644 --- a/tss-esapi/src/abstraction/transient/key_attestation.rs +++ b/tss-esapi/src/abstraction/transient/key_attestation.rs @@ -19,8 +19,22 @@ use std::convert::{TryFrom, TryInto}; #[derive(Debug)] /// Wrapper for the parameters needed by MakeCredential +/// +/// The 3rd party requesting proof that the key is indeed backed +/// by a TPM would perform a MakeCredential and would thus require +/// `name` and `attesting_key_pub` as inputs for that operation. +/// +/// `public` is not strictly needed, however it is returned as a +/// convenience block of data. Since the MakeCredential operation +/// bakes into the encrypted credential the identity of the key to +/// be attested via its `name`, the correctness of the `name` must +/// be verifiable by the said 3rd party. `public` bridges this gap: +/// +/// * it includes all the public parameters of the attested key +/// * can be hashed (in its marshaled form) with the name hash +/// (found by unmarshaling it) to obtain `name` pub struct MakeCredParams { - /// TPM name of the object + /// TPM name of the object being attested pub name: Vec, /// Encoding of the public parameters of the object whose name /// will be included in the credential computations diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index 142f136a..2b0d10d5 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -790,8 +790,10 @@ fn activate_credential_wrong_key() { drop(basic_ctx); // Create a new Transient key context and activate the credential + // Validation fails within the TPM because the credential HMAC is + // associated with a different object (so the integrity check fails). let mut ctx = create_ctx(); - let _ = ctx + let e = ctx .activate_credential( wrong_obj, None, @@ -799,6 +801,11 @@ fn activate_credential_wrong_key() { secret.value().to_vec(), ) .unwrap_err(); + if let Error::Tss2Error(e) = e { + assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Integrity)); + } else { + panic!("Got crate error ({}) when expecting an error from TPM.", e); + } } #[test] @@ -821,11 +828,23 @@ fn activate_credential_wrong_data() { params, }; - let _ = ctx + // No data (essentially wrong size) + let e = ctx .activate_credential(obj.clone(), None, vec![], vec![]) .unwrap_err(); + if let Error::Tss2Error(e) = e { + assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Size)); + } else { + panic!("Got crate error ({}) when expecting an error from TPM.", e); + } - let _ = ctx + // Correct size but gibberish + let e = ctx .activate_credential(obj, None, vec![0xaa; 52], vec![0x55; 256]) .unwrap_err(); + if let Error::Tss2Error(e) = e { + assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Value)); + } else { + panic!("Got crate error ({}) when expecting an error from TPM.", e); + } } From d523ce23b4b42637873c5dab474fba7556309578 Mon Sep 17 00:00:00 2001 From: Ionut Mihalcea Date: Thu, 18 Nov 2021 14:20:21 +0000 Subject: [PATCH 9/9] Adapt test case for swtpm Signed-off-by: Ionut Mihalcea --- .../abstraction_tests/transient_key_context_tests.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs index 2b0d10d5..ba0bcbbf 100644 --- a/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs +++ b/tss-esapi/tests/integration_tests/abstraction_tests/transient_key_context_tests.rs @@ -843,7 +843,11 @@ fn activate_credential_wrong_data() { .activate_credential(obj, None, vec![0xaa; 52], vec![0x55; 256]) .unwrap_err(); if let Error::Tss2Error(e) = e { - assert_eq!(e.kind(), Some(Tss2ResponseCodeKind::Value)); + // IBM software TPM returns Value, swtpm returns Failure... + assert!(matches!( + e.kind(), + Some(Tss2ResponseCodeKind::Value) | Some(Tss2ResponseCodeKind::Failure) + )); } else { panic!("Got crate error ({}) when expecting an error from TPM.", e); }