diff --git a/Cargo.toml b/Cargo.toml index 0d0748b..ab8ba9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ biscuit = "0.5.0-beta2" serde_json = "1.0" base64 = "0.12.1" atty = "0.2.14" +tpm2-policy = "0.1.0" diff --git a/src/main.rs b/src/main.rs index 36c9543..6322760 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::error::Error; use std::fmt; use std::fs; @@ -38,14 +38,16 @@ use tss_esapi::Context; use serde::{Deserialize, Serialize}; -pub fn serialize_as_base64_url_no_pad(bytes: &[u8], serializer: S) -> Result +use tpm2_policy::{TPMPolicyStep, SignedPolicyList, PublicKey}; + +fn serialize_as_base64_url_no_pad(bytes: &[u8], serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&base64::encode_config(bytes, base64::URL_SAFE_NO_PAD)) } -pub fn deserialize_as_base64_url_no_pad<'de, D>(deserializer: D) -> Result, D::Error> +fn deserialize_as_base64_url_no_pad<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -54,21 +56,6 @@ where }) } -pub fn serialize_as_base64(bytes: &[u8], serializer: S) -> Result -where - S: serde::Serializer, -{ - serializer.serialize_str(&base64::encode(bytes)) -} - -pub fn deserialize_as_base64<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - String::deserialize(deserializer) - .and_then(|string| base64::decode(&string).map_err(serde::de::Error::custom)) -} - fn tpm_sym_def(_ctx: &mut tss_esapi::Context) -> Result { Ok(tss_esapi::tss2_esys::TPMT_SYM_DEF { algorithm: tss_esapi::constants::TPM2_ALG_AES, @@ -87,6 +74,7 @@ enum PinError { JWE(biscuit::errors::Error), Base64Decoding(base64::DecodeError), Utf8(std::str::Utf8Error), + PolicyError(tpm2_policy::Error), } impl PinError {} @@ -120,12 +108,28 @@ impl fmt::Display for PinError { err.fmt(f) } PinError::NoCommand => write!(f, "No command provided"), + PinError::PolicyError(err) => { + write!(f, "Policy Error: ")?; + err.fmt(f) + } } } } impl Error for PinError {} +impl From for PinError { + fn from(err: std::io::Error) -> Self { + PinError::IO(err) + } +} + +impl From for PinError { + fn from(err: tpm2_policy::Error) -> Self { + PinError::PolicyError(err) + } +} + impl From<&'static str> for PinError { fn from(err: &'static str) -> Self { PinError::Text(err) @@ -138,12 +142,6 @@ impl From for PinError { } } -impl From for PinError { - fn from(err: std::io::Error) -> Self { - PinError::IO(err) - } -} - impl From for PinError { fn from(err: tss_esapi::response_code::Error) -> Self { PinError::TPM(err) @@ -168,23 +166,6 @@ impl From for PinError { } } -#[derive(Debug)] -enum TPMPolicyStep { - NoStep, - PCRs( - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm, - Vec, - Box, - ), - Authorized { - signkey: PublicKey, - policy_ref: Vec, - policies: Option, - next: Box, - }, - Or([Box; 8]), -} - fn create_and_set_tpm2_session( ctx: &mut tss_esapi::Context, session_type: tss_esapi::tss2_esys::TPM2_SE, @@ -199,7 +180,7 @@ fn create_and_set_tpm2_session( symdef, tss_esapi::constants::TPM2_ALG_SHA256, )?; - let session_attr = utils::TpmaSessionBuilder::new() + let session_attr = tss_esapi::utils::TpmaSessionBuilder::new() .with_flag(tss_esapi::constants::TPMA_SESSION_DECRYPT) .with_flag(tss_esapi::constants::TPMA_SESSION_ENCRYPT) .build(); @@ -211,275 +192,6 @@ fn create_and_set_tpm2_session( Ok(session) } -impl TPMPolicyStep { - /// Sends the generate policy to the TPM2, and sets the authorized policy as active - /// Returns the policy_digest for authInfo - fn send_policy( - self, - ctx: &mut tss_esapi::Context, - trial_policy: bool, - ) -> Result, PinError> { - let pol_type = if trial_policy { - tss_esapi::constants::TPM2_SE_TRIAL - } else { - tss_esapi::constants::TPM2_SE_POLICY - }; - - let symdef = tpm_sym_def(ctx)?; - - let session = ctx.start_auth_session( - ESYS_TR_NONE, - ESYS_TR_NONE, - &[], - pol_type, - symdef, - tss_esapi::constants::TPM2_ALG_SHA256, - )?; - let session_attr = utils::TpmaSessionBuilder::new() - .with_flag(tss_esapi::constants::TPMA_SESSION_DECRYPT) - .with_flag(tss_esapi::constants::TPMA_SESSION_ENCRYPT) - .build(); - ctx.tr_sess_set_attributes(session, session_attr)?; - - match self { - TPMPolicyStep::NoStep => { - create_and_set_tpm2_session(ctx, tss_esapi::constants::TPM2_SE_HMAC)?; - Ok(None) - } - _ => { - self._send_policy(ctx, session)?; - - let pol_digest = ctx.policy_get_digest(session)?; - - if trial_policy { - create_and_set_tpm2_session(ctx, tss_esapi::constants::TPM2_SE_HMAC)?; - } else { - ctx.set_sessions((session, ESYS_TR_NONE, ESYS_TR_NONE)); - } - Ok(Some(pol_digest)) - } - } - } - - fn _send_policy( - self, - ctx: &mut tss_esapi::Context, - policy_session: tss_esapi::tss2_esys::ESYS_TR, - ) -> Result<(), PinError> { - match self { - TPMPolicyStep::NoStep => Ok(()), - - TPMPolicyStep::PCRs(pcr_hash_alg, pcr_ids, next) => { - let pcr_ids: Result, PinError> = - pcr_ids.iter().map(|x| pcr_id_to_slot(x)).collect(); - let pcr_ids: Vec = pcr_ids?; - - let pcr_sel = tss_esapi::utils::PcrSelectionsBuilder::new() - .with_selection(pcr_hash_alg, &pcr_ids) - .build(); - - // Ensure PCR reading occurs with no sessions (we don't use audit sessions) - let old_ses = ctx.sessions(); - ctx.set_sessions((ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE)); - let (_update_counter, pcr_sel, pcr_data) = ctx.pcr_read(pcr_sel)?; - ctx.set_sessions(old_ses); - - let concatenated_pcr_values: Vec<&[u8]> = pcr_ids - .iter() - .map(|x| { - pcr_data - .pcr_bank(pcr_hash_alg) - .unwrap() - .pcr_value(*x) - .unwrap() - .value() - }) - .collect(); - let concatenated_pcr_values = concatenated_pcr_values.as_slice().concat(); - - let (hashed_data, _ticket) = ctx.hash( - &concatenated_pcr_values, - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha256, - tss_esapi::utils::Hierarchy::Owner, - )?; - - ctx.policy_pcr(policy_session, &hashed_data, pcr_sel)?; - next._send_policy(ctx, policy_session) - } - - TPMPolicyStep::Authorized { - signkey, - policy_ref, - policies, - next, - } => { - let policy_ref = tss_esapi::utils::Digest::try_from(policy_ref)?; - - let tpm_signkey = tss_esapi::tss2_esys::TPM2B_PUBLIC::try_from(&signkey)?; - let loaded_key = - ctx.load_external_public(&tpm_signkey, tss_esapi::utils::Hierarchy::Owner)?; - let loaded_key_name = ctx.tr_get_name(loaded_key)?; - - let (approved_policy, check_ticket) = match policies { - None => { - /* Some TPMs don't seem to like the Null ticket.. Let's just use a dummy - let null_ticket = tss_esapi::tss2_esys::TPMT_TK_VERIFIED { - tag: tss_esapi::constants::TPM2_ST_VERIFIED, - hierarchy: tss_esapi::tss2_esys::ESYS_TR_RH_NULL, - digest: tss_esapi::tss2_esys::TPM2B_DIGEST { - size: 32, - buffer: [0; 64], - }, - }; - */ - let dummy_ticket = get_dummy_ticket(ctx); - (tss_esapi::utils::Digest::try_from(vec![])?, dummy_ticket) - } - Some(policies) => find_and_play_applicable_policy( - ctx, - &policies, - policy_session, - policy_ref.value(), - signkey.get_signing_scheme(), - loaded_key, - )?, - }; - - ctx.policy_authorize( - policy_session, - approved_policy, - tss_esapi::tss2_esys::TPM2B_DIGEST::try_from(policy_ref)?, - loaded_key_name, - check_ticket, - )?; - - next._send_policy(ctx, policy_session) - } - - _ => Err(PinError::Text("Policy not implemented")), - } - } -} - -fn find_and_play_applicable_policy( - ctx: &mut tss_esapi::Context, - policies: &[SignedPolicy], - policy_session: ESYS_TR, - policy_ref: &[u8], - scheme: utils::AsymSchemeUnion, - loaded_key: ESYS_TR, -) -> Result< - ( - tss_esapi::utils::Digest, - tss_esapi::tss2_esys::TPMT_TK_VERIFIED, - ), - PinError, -> { - for policy in policies { - if policy.policy_ref != policy_ref { - continue; - } - - if let Some(policy_digest) = play_policy(ctx, &policy, policy_session)? { - // aHash ≔ H_{aHashAlg}(approvedPolicy || policyRef) - let mut ahash = Vec::new(); - ahash.write_all(&policy_digest)?; - ahash.write_all(&policy_ref)?; - - let ahash = ctx - .hash( - &ahash, - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha256, - tss_esapi::utils::Hierarchy::Null, - )? - .0; - let signature = tss_esapi::utils::Signature { - scheme, - signature: tss_esapi::utils::SignatureData::RsaSignature(policy.signature.clone()), - }; - let tkt = ctx.verify_signature(loaded_key, &ahash, &signature.try_into()?)?; - - return Ok((policy_digest, tkt)); - } - } - - Err(PinError::Text("No matching authorized policy found")) -} - -// This function would do a simple check whether the policy has a chance for success. -// It does explicitly not change policy_session -fn check_policy_feasibility(_ctx: &mut tss_esapi::Context, _policy: &SignedPolicy) -> Result { - Ok(true) - // TODO: Implement this, to check whether the PCRs in this branch would match -} - -fn play_policy( - ctx: &mut tss_esapi::Context, - policy: &SignedPolicy, - policy_session: ESYS_TR, -) -> Result, PinError> { - if !check_policy_feasibility(ctx, policy)? { - return Ok(None) - } - - for step in &policy.steps { - let tpmstep = TPMPolicyStep::try_from(step)?; - tpmstep._send_policy(ctx, policy_session)?; - } - - Ok(Some(ctx.policy_get_digest(policy_session)?)) -} - -// It turns out that a Null ticket does not work for some TPMs, so let's just generate -// a dummy ticket. This is a valid ticket, but over a totally useless piece of data. -fn get_dummy_ticket(context: &mut tss_esapi::Context) -> tss_esapi::tss2_esys::TPMT_TK_VERIFIED { - let old_ses = context.sessions(); - context.set_sessions((ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE)); - create_and_set_tpm2_session(context, tss_esapi::constants::TPM2_SE_HMAC).unwrap(); - - let signing_key_pub = utils::create_unrestricted_signing_rsa_public( - tss_esapi::utils::AsymSchemeUnion::RSASSA( - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha256, - ), - 2048, - 0, - ) - .unwrap(); - - let key_handle = context - .create_primary_key(ESYS_TR_RH_OWNER, &signing_key_pub, &[], &[], &[], &[]) - .unwrap(); - let ahash = context - .hash( - &[0x1, 0x2], - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha256, - tss_esapi::utils::Hierarchy::Null, - ) - .unwrap() - .0; - - let scheme = tss_esapi::tss2_esys::TPMT_SIG_SCHEME { - scheme: tss_esapi::constants::TPM2_ALG_NULL, - details: Default::default(), - }; - let validation = tss_esapi::tss2_esys::TPMT_TK_HASHCHECK { - tag: tss_esapi::constants::TPM2_ST_HASHCHECK, - hierarchy: tss_esapi::constants::TPM2_RH_NULL, - digest: Default::default(), - }; - // A signature over just the policy_digest, since the policy_ref is empty - let signature = context - .sign(key_handle, &ahash, scheme, &validation) - .unwrap(); - let tkt = context - .verify_signature(key_handle, &ahash, &signature.try_into().unwrap()) - .unwrap(); - - context.set_sessions(old_ses); - - tkt -} - #[derive(Serialize, Deserialize, std::fmt::Debug)] struct TPM2Config { hash: Option, @@ -525,22 +237,6 @@ fn get_authorized_policy_step( }) } -impl TryFrom<&SignedPolicyStep> for TPMPolicyStep { - type Error = PinError; - - fn try_from(spolicy: &SignedPolicyStep) -> Result { - match spolicy { - SignedPolicyStep::PCRs{pcr_ids, hash_algorithm, value: _} => { - Ok(TPMPolicyStep::PCRs( - get_pcr_hash_alg_from_name(Some(&hash_algorithm)), - pcr_ids.iter().map(|x| *x as u64).collect(), - Box::new(TPMPolicyStep::NoStep), - )) - }, - } - } -} - impl TryFrom<&TPM2Config> for TPMPolicyStep { type Error = PinError; @@ -582,36 +278,6 @@ impl TryFrom<&TPM2Config> for TPMPolicyStep { } } -fn pcr_id_to_slot(pcr: &u64) -> Result { - match pcr { - 0 => Ok(tss_esapi::utils::PcrSlot::Slot0), - 1 => Ok(tss_esapi::utils::PcrSlot::Slot1), - 2 => Ok(tss_esapi::utils::PcrSlot::Slot2), - 3 => Ok(tss_esapi::utils::PcrSlot::Slot3), - 4 => Ok(tss_esapi::utils::PcrSlot::Slot4), - 5 => Ok(tss_esapi::utils::PcrSlot::Slot5), - 6 => Ok(tss_esapi::utils::PcrSlot::Slot6), - 7 => Ok(tss_esapi::utils::PcrSlot::Slot7), - 8 => Ok(tss_esapi::utils::PcrSlot::Slot8), - 9 => Ok(tss_esapi::utils::PcrSlot::Slot9), - 10 => Ok(tss_esapi::utils::PcrSlot::Slot10), - 11 => Ok(tss_esapi::utils::PcrSlot::Slot11), - 12 => Ok(tss_esapi::utils::PcrSlot::Slot12), - 13 => Ok(tss_esapi::utils::PcrSlot::Slot13), - 14 => Ok(tss_esapi::utils::PcrSlot::Slot14), - 15 => Ok(tss_esapi::utils::PcrSlot::Slot15), - 16 => Ok(tss_esapi::utils::PcrSlot::Slot16), - 17 => Ok(tss_esapi::utils::PcrSlot::Slot17), - 18 => Ok(tss_esapi::utils::PcrSlot::Slot18), - 19 => Ok(tss_esapi::utils::PcrSlot::Slot19), - 20 => Ok(tss_esapi::utils::PcrSlot::Slot20), - 21 => Ok(tss_esapi::utils::PcrSlot::Slot21), - 22 => Ok(tss_esapi::utils::PcrSlot::Slot22), - 23 => Ok(tss_esapi::utils::PcrSlot::Slot23), - _ => Err(PinError::Text("Invalid PCR slot requested")), - } -} - fn get_pcr_hash_alg_from_name( name: Option<&String>, ) -> tss_esapi::utils::algorithm_specifiers::HashingAlgorithm { @@ -1096,172 +762,6 @@ fn get_key_public(key_type: &str) -> Result, - hash_algorithm: String, - #[serde( - deserialize_with = "deserialize_as_base64", - serialize_with = "serialize_as_base64" - )] - value: Vec, - }, -} - -#[derive(Debug, Serialize, Deserialize)] -struct SignedPolicy { - // policy_ref contains the policy_ref used in the aHash, used to determine the policy to use from a list - #[serde( - deserialize_with = "deserialize_as_base64", - serialize_with = "serialize_as_base64" - )] - policy_ref: Vec, - // steps contains the policy steps that are signed - steps: Vec, - // signature contains the signature over aHash - #[serde( - deserialize_with = "deserialize_as_base64", - serialize_with = "serialize_as_base64" - )] - signature: Vec, -} - -type SignedPolicyList = Vec; - -#[derive(Debug, Serialize, Deserialize)] -enum RSAPublicKeyScheme { - RSAPSS, - RSASSA, -} - -impl RSAPublicKeyScheme { - fn to_scheme(&self, hash_algo: &HashAlgo) -> tss_esapi::utils::AsymSchemeUnion { - match self { - RSAPublicKeyScheme::RSAPSS => { - tss_esapi::utils::AsymSchemeUnion::RSAPSS(hash_algo.into()) - } - RSAPublicKeyScheme::RSASSA => { - tss_esapi::utils::AsymSchemeUnion::RSASSA(hash_algo.into()) - } - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum HashAlgo { - SHA1, - SHA256, - SHA384, - SHA512, - SM3_256, - SHA3_256, - SHA3_384, - SHA3_512, -} - -impl HashAlgo { - fn to_tpmi_alg_hash(&self) -> tss_esapi::tss2_esys::TPMI_ALG_HASH { - let alg: tss_esapi::utils::algorithm_specifiers::HashingAlgorithm = self.into(); - alg.into() - } -} - -impl From<&HashAlgo> for tss_esapi::utils::algorithm_specifiers::HashingAlgorithm { - fn from(halg: &HashAlgo) -> Self { - match halg { - HashAlgo::SHA1 => tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha1, - HashAlgo::SHA256 => tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha256, - HashAlgo::SHA384 => tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha384, - HashAlgo::SHA512 => tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha512, - HashAlgo::SM3_256 => tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sm3_256, - HashAlgo::SHA3_256 => { - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha3_256 - } - HashAlgo::SHA3_384 => { - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha3_384 - } - HashAlgo::SHA3_512 => { - tss_esapi::utils::algorithm_specifiers::HashingAlgorithm::Sha3_512 - } - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -enum PublicKey { - RSA { - scheme: RSAPublicKeyScheme, - hashing_algo: HashAlgo, - exponent: u32, - #[serde( - deserialize_with = "deserialize_as_base64", - serialize_with = "serialize_as_base64" - )] - modulus: Vec, - }, -} - -impl PublicKey { - fn get_signing_scheme(&self) -> tss_esapi::utils::AsymSchemeUnion { - match self { - PublicKey::RSA { - scheme, - hashing_algo, - exponent: _, - modulus: _, - } => scheme.to_scheme(hashing_algo), - } - } -} - -impl TryFrom<&PublicKey> for tss_esapi::tss2_esys::TPM2B_PUBLIC { - type Error = PinError; - - fn try_from(publickey: &PublicKey) -> Result { - match publickey { - PublicKey::RSA { - scheme, - hashing_algo, - modulus, - exponent, - } => { - let mut object_attributes = tss_esapi::utils::ObjectAttributes(0); - object_attributes.set_fixed_tpm(false); - object_attributes.set_fixed_parent(false); - object_attributes.set_sensitive_data_origin(false); - object_attributes.set_user_with_auth(true); - object_attributes.set_decrypt(false); - object_attributes.set_sign_encrypt(true); - object_attributes.set_restricted(false); - - let len = modulus.len(); - let mut buffer = [0_u8; 512]; - buffer[..len].clone_from_slice(&modulus[..len]); - let rsa_uniq = Box::new(tss_esapi::tss2_esys::TPM2B_PUBLIC_KEY_RSA { - size: len as u16, - buffer, - }); - - Ok(tss_esapi::utils::Tpm2BPublicBuilder::new() - .with_type(tss_esapi::constants::TPM2_ALG_RSA) - .with_name_alg(hashing_algo.to_tpmi_alg_hash()) - .with_parms(tss_esapi::utils::PublicParmsUnion::RsaDetail( - tss_esapi::utils::TpmsRsaParmsBuilder::new_unrestricted_signing_key( - scheme.to_scheme(&hashing_algo), - (modulus.len() * 8) as u16, - *exponent, - ) - .build()?, - )) - .with_object_attributes(object_attributes) - .with_unique(tss_esapi::utils::PublicIdUnion::Rsa(rsa_uniq)) - .build()?) - } - } - } -} - fn perform_decrypt(input: &str) -> Result<(), PinError> { let token = biscuit::Compact::decode(input.trim()); let hdr: biscuit::jwe::Header = token.part(0)?;