use std::convert::{TryFrom, TryInto};
use std::io::Write;
use tss_esapi::constants::algorithm::HashingAlgorithm;
use tss_esapi::constants::tss as tss_constants;
use tss_esapi::structures::{Digest, MaxBuffer, Nonce, PcrSelectionListBuilder, PcrSlot};
use tss_esapi::tss2_esys::{ESYS_TR, ESYS_TR_NONE, ESYS_TR_RH_OWNER};
use tss_esapi::utils::AsymSchemeUnion;
use tss_esapi::utils::TpmaSessionBuilder;
use serde::{Deserialize, Serialize};
fn serialize_as_base64<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&base64::encode(bytes))
}
fn deserialize_as_base64<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)
.and_then(|string| base64::decode(&string).map_err(serde::de::Error::custom))
}
mod error;
pub use error::Error;
#[derive(Debug, Serialize, Deserialize)]
pub enum SignedPolicyStep {
PCRs {
pcr_ids: Vec<u16>,
hash_algorithm: String,
#[serde(
deserialize_with = "deserialize_as_base64",
serialize_with = "serialize_as_base64"
)]
value: Vec<u8>,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SignedPolicy {
#[serde(
deserialize_with = "deserialize_as_base64",
serialize_with = "serialize_as_base64"
)]
policy_ref: Vec<u8>,
steps: Vec<SignedPolicyStep>,
#[serde(
deserialize_with = "deserialize_as_base64",
serialize_with = "serialize_as_base64"
)]
signature: Vec<u8>,
}
pub type SignedPolicyList = Vec<SignedPolicy>;
#[derive(Debug)]
pub enum TPMPolicyStep {
NoStep,
PCRs(HashingAlgorithm, Vec<u64>, Box<TPMPolicyStep>),
Authorized {
signkey: PublicKey,
policy_ref: Vec<u8>,
policies: Option<SignedPolicyList>,
next: Box<TPMPolicyStep>,
},
Or([Box<TPMPolicyStep>; 8]),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RSAPublicKeyScheme {
RSAPSS,
RSASSA,
}
impl RSAPublicKeyScheme {
fn to_scheme(&self, hash_algo: &HashAlgo) -> AsymSchemeUnion {
match self {
RSAPublicKeyScheme::RSAPSS => AsymSchemeUnion::RSAPSS(hash_algo.into()),
RSAPublicKeyScheme::RSASSA => 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: HashingAlgorithm = self.into();
alg.into()
}
}
impl From<&HashAlgo> for HashingAlgorithm {
fn from(halg: &HashAlgo) -> Self {
match halg {
HashAlgo::SHA1 => HashingAlgorithm::Sha1,
HashAlgo::SHA256 => HashingAlgorithm::Sha256,
HashAlgo::SHA384 => HashingAlgorithm::Sha384,
HashAlgo::SHA512 => HashingAlgorithm::Sha512,
HashAlgo::SM3_256 => HashingAlgorithm::Sm3_256,
HashAlgo::SHA3_256 => HashingAlgorithm::Sha3_256,
HashAlgo::SHA3_384 => HashingAlgorithm::Sha3_384,
HashAlgo::SHA3_512 => HashingAlgorithm::Sha3_512,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PublicKey {
RSA {
scheme: RSAPublicKeyScheme,
hashing_algo: HashAlgo,
exponent: u32,
#[serde(
deserialize_with = "deserialize_as_base64",
serialize_with = "serialize_as_base64"
)]
modulus: Vec<u8>,
},
}
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 = Error;
fn try_from(publickey: &PublicKey) -> Result<Self, Self::Error> {
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_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 tpm_sym_def(_ctx: &mut tss_esapi::Context) -> tss_esapi::tss2_esys::TPMT_SYM_DEF {
tss_esapi::tss2_esys::TPMT_SYM_DEF {
algorithm: tss_constants::TPM2_ALG_AES,
keyBits: tss_esapi::tss2_esys::TPMU_SYM_KEY_BITS { aes: 128 },
mode: tss_esapi::tss2_esys::TPMU_SYM_MODE {
aes: tss_constants::TPM2_ALG_CFB,
},
}
}
fn create_and_set_tpm2_session(
ctx: &mut tss_esapi::Context,
session_type: tss_esapi::tss2_esys::TPM2_SE,
) -> Result<ESYS_TR, Error> {
let symdef = tpm_sym_def(ctx);
let session = ctx.start_auth_session(
ESYS_TR_NONE,
ESYS_TR_NONE,
None,
session_type,
symdef,
tss_constants::TPM2_ALG_SHA256,
)?;
let session_attr = TpmaSessionBuilder::new()
.with_flag(tss_constants::TPMA_SESSION_DECRYPT)
.with_flag(tss_constants::TPMA_SESSION_ENCRYPT)
.build();
ctx.tr_sess_set_attributes(session, session_attr)?;
ctx.set_sessions((session, ESYS_TR_NONE, ESYS_TR_NONE));
Ok(session)
}
fn pcr_id_to_slot(pcr: &u64) -> Result<PcrSlot, Error> {
match pcr {
0 => Ok(PcrSlot::Slot0),
1 => Ok(PcrSlot::Slot1),
2 => Ok(PcrSlot::Slot2),
3 => Ok(PcrSlot::Slot3),
4 => Ok(PcrSlot::Slot4),
5 => Ok(PcrSlot::Slot5),
6 => Ok(PcrSlot::Slot6),
7 => Ok(PcrSlot::Slot7),
8 => Ok(PcrSlot::Slot8),
9 => Ok(PcrSlot::Slot9),
10 => Ok(PcrSlot::Slot10),
11 => Ok(PcrSlot::Slot11),
12 => Ok(PcrSlot::Slot12),
13 => Ok(PcrSlot::Slot13),
14 => Ok(PcrSlot::Slot14),
15 => Ok(PcrSlot::Slot15),
16 => Ok(PcrSlot::Slot16),
17 => Ok(PcrSlot::Slot17),
18 => Ok(PcrSlot::Slot18),
19 => Ok(PcrSlot::Slot19),
20 => Ok(PcrSlot::Slot20),
21 => Ok(PcrSlot::Slot21),
22 => Ok(PcrSlot::Slot22),
23 => Ok(PcrSlot::Slot23),
_ => Err(Error::InvalidValue),
}
}
impl TPMPolicyStep {
pub fn send_policy(
self,
ctx: &mut tss_esapi::Context,
trial_policy: bool,
) -> Result<Option<Digest>, Error> {
let pol_type = if trial_policy {
tss_constants::TPM2_SE_TRIAL
} else {
tss_constants::TPM2_SE_POLICY
};
let symdef = tpm_sym_def(ctx);
let session = ctx.start_auth_session(
ESYS_TR_NONE,
ESYS_TR_NONE,
None,
pol_type,
symdef,
tss_constants::TPM2_ALG_SHA256,
)?;
let session_attr = TpmaSessionBuilder::new()
.with_flag(tss_constants::TPMA_SESSION_DECRYPT)
.with_flag(tss_constants::TPMA_SESSION_ENCRYPT)
.build();
ctx.tr_sess_set_attributes(session, session_attr)?;
match self {
TPMPolicyStep::NoStep => {
create_and_set_tpm2_session(ctx, tss_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_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<(), Error> {
match self {
TPMPolicyStep::NoStep => Ok(()),
TPMPolicyStep::PCRs(pcr_hash_alg, pcr_ids, next) => {
let pcr_ids: Result<Vec<PcrSlot>, Error> =
pcr_ids.iter().map(|x| pcr_id_to_slot(x)).collect();
let pcr_ids: Vec<PcrSlot> = pcr_ids?;
let pcr_sel = PcrSelectionListBuilder::new()
.with_selection(pcr_hash_alg, &pcr_ids)
.build();
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 concatenated_pcr_values = MaxBuffer::try_from(concatenated_pcr_values)?;
let (hashed_data, _ticket) = ctx.hash(
&concatenated_pcr_values,
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 = Nonce::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 => {
let dummy_ticket = get_dummy_ticket(ctx);
(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,
&policy_ref,
&loaded_key_name,
check_ticket,
)?;
next._send_policy(ctx, policy_session)
}
_ => Err(Error::NotImplemented("Policy type".to_string())),
}
}
}
fn find_and_play_applicable_policy(
ctx: &mut tss_esapi::Context,
policies: &[SignedPolicy],
policy_session: ESYS_TR,
policy_ref: &[u8],
scheme: AsymSchemeUnion,
loaded_key: ESYS_TR,
) -> Result<(Digest, tss_esapi::tss2_esys::TPMT_TK_VERIFIED), Error> {
for policy in policies {
if policy.policy_ref != policy_ref {
continue;
}
if let Some(policy_digest) = play_policy(ctx, &policy, policy_session)? {
let mut ahash = Vec::new();
ahash.write_all(&policy_digest)?;
ahash.write_all(&policy_ref)?;
let ahash = MaxBuffer::try_from(ahash)?;
let ahash = ctx
.hash(
&ahash,
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(Error::NoMatchingPolicy)
}
fn check_policy_feasibility(
_ctx: &mut tss_esapi::Context,
_policy: &SignedPolicy,
) -> Result<bool, Error> {
Ok(true)
}
fn play_policy(
ctx: &mut tss_esapi::Context,
policy: &SignedPolicy,
policy_session: ESYS_TR,
) -> Result<Option<Digest>, Error> {
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)?))
}
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_constants::TPM2_SE_HMAC).unwrap();
let signing_key_pub = tss_esapi::utils::create_unrestricted_signing_rsa_public(
tss_esapi::utils::AsymSchemeUnion::RSASSA(HashingAlgorithm::Sha256),
2048,
0,
)
.unwrap();
let key_handle = context
.create_primary_key(ESYS_TR_RH_OWNER, &signing_key_pub, None, None, None, &[])
.unwrap();
let ahash = context
.hash(
&MaxBuffer::try_from(vec![0x1, 0x2]).unwrap(),
HashingAlgorithm::Sha256,
tss_esapi::utils::Hierarchy::Null,
)
.unwrap()
.0;
let scheme = tss_esapi::tss2_esys::TPMT_SIG_SCHEME {
scheme: tss_constants::TPM2_ALG_NULL,
details: Default::default(),
};
let validation = tss_esapi::tss2_esys::TPMT_TK_HASHCHECK {
tag: tss_constants::TPM2_ST_HASHCHECK,
hierarchy: tss_constants::TPM2_RH_NULL,
digest: Default::default(),
};
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
}
fn get_pcr_hash_alg_from_name(name: Option<&String>) -> HashingAlgorithm {
match name {
None => HashingAlgorithm::Sha256,
Some(val) => match val.to_lowercase().as_str() {
"sha1" => HashingAlgorithm::Sha1,
"sha256" => HashingAlgorithm::Sha256,
"sha384" => HashingAlgorithm::Sha384,
"sha512" => HashingAlgorithm::Sha512,
_ => panic!(format!("Unsupported hash algo: {:?}", name)),
},
}
}
impl TryFrom<&SignedPolicyStep> for TPMPolicyStep {
type Error = Error;
fn try_from(spolicy: &SignedPolicyStep) -> Result<Self, Error> {
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),
)),
}
}
}