From e3213c8a792070419c50cebd927c237d9e21126f Mon Sep 17 00:00:00 2001 From: Praveen K Paladugu Date: Thu, 8 Sep 2022 06:02:26 +0000 Subject: [PATCH] tpm: Add library module Add structures and related methods to process Ctrl requests and responses from swtpm to tpm library. Signed-off-by: Praveen K Paladugu Co-authored-by: Sean Yoo --- Cargo.lock | 11 ++ Cargo.toml | 1 + tpm/Cargo.toml | 12 ++ tpm/src/lib.rs | 434 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 458 insertions(+) create mode 100644 tpm/Cargo.toml create mode 100644 tpm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3165eeefa..fa2f808c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,7 @@ dependencies = [ "signal-hook", "test_infra", "thiserror", + "tpm", "tracer", "vm-memory", "vmm", @@ -1159,6 +1160,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tpm" +version = "0.1.0" +dependencies = [ + "anyhow", + "byteorder", + "log", + "thiserror", +] + [[package]] name = "tracer" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a958ebce0..9ae20c019 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ seccompiler = "0.3.0" serde_json = "1.0.87" signal-hook = "0.3.14" thiserror = "1.0.37" +tpm = { path = "tpm"} tracer = { path = "tracer" } vmm = { path = "vmm" } vmm-sys-util = "0.10.0" diff --git a/tpm/Cargo.toml b/tpm/Cargo.toml new file mode 100644 index 000000000..6aa00ee24 --- /dev/null +++ b/tpm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tpm" +edition = "2021" +authors = ["Microsoft Authors"] +license = "Apache-2.0" +version = "0.1.0" + +[dependencies] +anyhow = "1.0.56" +byteorder = "1.4.3" +log = "0.4.14" +thiserror = "1.0.30" diff --git a/tpm/src/lib.rs b/tpm/src/lib.rs new file mode 100644 index 000000000..3f550d05f --- /dev/null +++ b/tpm/src/lib.rs @@ -0,0 +1,434 @@ +// Copyright © 2022, Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::anyhow; +use byteorder::{BigEndian, ReadBytesExt}; +use std::convert::TryInto; +use thiserror::Error; + +pub const TPM_CRB_BUFFER_MAX: usize = 3968; // 0x1_000 - 0x80 +pub const TPM_SUCCESS: u32 = 0x0; + +/* + * Structures required to process Request and Responses of Control commands + * used by control channel over UNIX socket interface + * + * All messages contain big-endian data. + * + * Reference: https://github.com/stefanberger/swtpm/blob/master/man/man3/swtpm_ioctls.pod + */ +#[derive(Debug, Clone, Copy)] +pub enum Commands { + CmdGetCapability = 1, + CmdInit, + CmdShutdown, + CmdGetTpmEstablished, + CmdSetLocality, + CmdHashStart, + CmdHashData, + CmdHashEnd, + CmdCancelTpmCmd, + CmdStoreVolatile, + CmdResetTpmEstablished, + CmdGetStateBlob, + CmdSetStateBlob, + CmdStop, + CmdGetConfig, + CmdSetDatafd, + CmdSetBufferSize, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("Failed converting buf to PTM : {0}")] + ConvertToPtm(#[source] anyhow::Error), +} +type Result = anyhow::Result; + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum MemberType { + Request, + Response, + Error, + Cap, +} + +pub trait Ptm { + // Get Member Type + fn get_member_type(&self) -> MemberType; + + // Set Member Type + fn set_member_type(&mut self, mem: MemberType); + + // Convert PTM Request to bytes to be sent to tpm + fn ptm_to_request(&self) -> Vec; + + // Update PTM from tpm's reponse + fn update_ptm_with_response(&mut self, buf: &[u8]) -> Result<()>; + + // Update tpm result + fn set_result_code(&mut self, res: u32); + + fn get_result_code(&self) -> u32; +} + +/* + * Every response for a tpm Control Command execution must hold tpm return + * code (PtmResult) as its first element. + * Based on the type of input Control Command additional data could be + * appended to the response. + */ +pub type PtmResult = u32; + +impl Ptm for PtmResult { + fn ptm_to_request(&self) -> Vec { + let buf: Vec = Vec::::new(); + buf + } + + fn get_member_type(&self) -> MemberType { + MemberType::Response + } + + fn update_ptm_with_response(&mut self, buf: &[u8]) -> Result<()> { + if buf.len() < 4 { + return Err(Error::ConvertToPtm(anyhow!( + "PtmRes buffer is of insufficient length. Buffer length should be atleast 4" + ))); + } + + *self = u32::from_be_bytes(buf[0..4].try_into().unwrap()); + Ok(()) + } + + fn set_member_type(&mut self, _mem: MemberType) {} + + fn set_result_code(&mut self, res: u32) { + *self = res; + } + + fn get_result_code(&self) -> u32 { + *self + } +} + +/* GET_CAPABILITY Response */ +pub type PtmCap = u64; +impl Ptm for PtmCap { + fn ptm_to_request(&self) -> Vec { + // tpm's GetCapability call doesn't need any supporting message + // return an empty Buffer + let buf: Vec = Vec::::new(); + buf + } + + fn get_member_type(&self) -> MemberType { + MemberType::Cap + } + + fn update_ptm_with_response(&mut self, mut buf: &[u8]) -> Result<()> { + let buf_len = buf.len(); + if buf_len != 8 { + return Err(Error::ConvertToPtm(anyhow!( + "Response for GetCapability cmd is of incorrect length: {:?}. Response buffer should be 8 bytes long", + buf_len))); + } + *self = buf.read_u64::().unwrap(); + Ok(()) + } + + fn set_member_type(&mut self, _mem: MemberType) {} + + fn set_result_code(&mut self, _res: u32) {} + + fn get_result_code(&self) -> u32 { + ((*self) >> 32) as u32 + } +} + +/* GET_TPMESTABLISHED Reponse */ +#[derive(Debug)] +pub struct PtmEstResp { + pub bit: u8, +} + +#[derive(Debug)] +pub struct PtmEst { + member: MemberType, + pub resp: PtmEstResp, + pub result_code: PtmResult, +} + +impl PtmEst { + pub fn new() -> Self { + Self { + member: MemberType::Response, + result_code: 0, + resp: PtmEstResp { bit: 0 }, + } + } +} + +impl Default for PtmEst { + fn default() -> Self { + Self::new() + } +} + +impl Ptm for PtmEst { + fn ptm_to_request(&self) -> Vec { + // tpm's GetTpmEstablished call doesn't need any supporting message + // return an empty Buffer + let buf: Vec = Vec::::new(); + buf + } + + fn get_member_type(&self) -> MemberType { + self.member + } + + fn update_ptm_with_response(&mut self, buf: &[u8]) -> Result<()> { + if buf.len() < 5 { + return Err(Error::ConvertToPtm(anyhow!( + "Response for GetTpmEstablished cmd is of incorrect length. Response buffer should be 5 bytes long" + ))); + } + let mut res = &buf[0..4]; + self.set_result_code(res.read_u32::().unwrap()); + let bit = &buf[4]; + self.resp.bit = *bit; + Ok(()) + } + + fn set_member_type(&mut self, _mem: MemberType) {} + + fn set_result_code(&mut self, res: u32) { + self.result_code = res + } + + fn get_result_code(&self) -> u32 { + self.result_code + } +} + +/* INIT Response */ + +#[derive(Debug)] +pub struct PtmInit { + pub member: MemberType, + /* request */ + pub init_flags: u32, + /* response */ + pub result_code: PtmResult, +} + +impl Default for PtmInit { + fn default() -> Self { + Self::new() + } +} + +impl PtmInit { + pub fn new() -> Self { + Self { + member: MemberType::Request, + init_flags: 0, + result_code: 0, + } + } +} + +impl Ptm for PtmInit { + fn ptm_to_request(&self) -> Vec { + let mut buf: Vec = Vec::::new(); + buf.extend_from_slice(&self.init_flags.to_be_bytes()); + buf + } + + fn get_member_type(&self) -> MemberType { + self.member + } + + fn update_ptm_with_response(&mut self, buf: &[u8]) -> Result<()> { + if buf.len() != 4 { + return Err(Error::ConvertToPtm(anyhow!( + "Response for Init cmd is of incorrect length. Response buffer should be 4 bytes long" + ))); + } + self.set_member_type(MemberType::Response); + let mut res = &buf[0..4]; + self.set_result_code(res.read_u32::().unwrap()); + Ok(()) + } + + fn set_member_type(&mut self, mem: MemberType) { + self.member = mem + } + + fn set_result_code(&mut self, res: u32) { + self.result_code = res + } + + fn get_result_code(&self) -> u32 { + self.result_code + } +} + +/* + * PTM_SET_BUFFERSIZE: Set the buffer size to be used by the tpm. + * A 0 on input queries for the current buffer size. Any other + * number will try to set the buffer size. The returned number is + * the buffer size that will be used, which can be larger than the + * requested one, if it was below the minimum, or smaller than the + * requested one, if it was above the maximum. + * + * SET_BUFFERSIZE Response + */ +#[derive(Debug)] +pub struct PtmSBSReq { + buffersize: u32, +} + +#[derive(Debug)] +pub struct PtmSBSResp { + bufsize: u32, + minsize: u32, + maxsize: u32, +} + +#[derive(Debug)] +pub struct PtmSetBufferSize { + pub mem: MemberType, + /* request */ + pub req: PtmSBSReq, + /* response */ + pub resp: PtmSBSResp, + pub result_code: PtmResult, +} + +impl PtmSetBufferSize { + pub fn new(req_buffsize: u32) -> Self { + Self { + mem: MemberType::Request, + req: PtmSBSReq { + buffersize: req_buffsize, + }, + resp: PtmSBSResp { + bufsize: 0, + minsize: 0, + maxsize: 0, + }, + result_code: 0, + } + } + pub fn get_bufsize(&self) -> u32 { + self.resp.bufsize + } +} + +impl Ptm for PtmSetBufferSize { + fn ptm_to_request(&self) -> Vec { + let mut buf: Vec = Vec::::new(); + buf.extend_from_slice(&self.req.buffersize.to_be_bytes()); + buf + } + + fn get_member_type(&self) -> MemberType { + self.mem + } + + fn update_ptm_with_response(&mut self, buf: &[u8]) -> Result<()> { + if buf.len() != 16 { + return Err(Error::ConvertToPtm(anyhow!( + "Response for CmdSetBufferSize cmd is of incorrect length. Response buffer should be 16 bytes long" + ))); + } + self.set_member_type(MemberType::Response); + let mut res = &buf[0..4]; + self.set_result_code(res.read_u32::().unwrap()); + + let mut bufsize = &buf[4..8]; + self.resp.bufsize = bufsize.read_u32::().unwrap(); + + let mut minsize = &buf[8..12]; + self.resp.minsize = minsize.read_u32::().unwrap(); + + let mut maxsize = &buf[12..16]; + self.resp.maxsize = maxsize.read_u32::().unwrap(); + + Ok(()) + } + + fn set_member_type(&mut self, mem: MemberType) { + self.mem = mem + } + + fn set_result_code(&mut self, res: u32) { + self.result_code = res + } + + fn get_result_code(&self) -> u32 { + self.result_code + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_ptmresult() -> Result<()> { + let buf: &[u8] = &[0, 0, 0, 1]; + let mut result_code: PtmResult = 0; + result_code.update_ptm_with_response(buf)?; + assert_eq!(result_code.get_result_code(), 0x1); + Ok(()) + } + #[test] + fn test_ptmcap() -> Result<()> { + let mut cap: PtmCap = 0x0; + let buf: &[u8] = &[0, 0, 0, 0xE, 0, 0, 0xFF, 0xFF]; + cap.update_ptm_with_response(buf)?; + assert_eq!(cap.get_result_code(), 0xE); + Ok(()) + } + #[test] + fn test_ptmest() -> Result<()> { + let mut est: PtmEst = PtmEst::new(); + let buf: &[u8] = &[0, 0, 0xE, 0, 0xC, 0, 1, 1]; + est.update_ptm_with_response(buf)?; + assert_eq!(est.get_result_code(), 0xE00); + assert_eq!(est.resp.bit, 0xC); + Ok(()) + } + #[test] + /*PtmInit Testing */ + fn test_ptminit() -> Result<()> { + let mut init: PtmInit = PtmInit::new(); + init.init_flags = 0x1; + let buf = init.ptm_to_request(); + assert_eq!(buf, [0x0, 0x0, 0x0, 0x1]); + let response_buf: &[u8] = &[0, 0, 0xE, 0]; + init.update_ptm_with_response(response_buf)?; + assert_eq!(init.get_result_code(), 0xE00); + Ok(()) + } + #[test] + /* PtmSetBufferSize Testing */ + fn test_ptmsetbuffersize() -> Result<()> { + let mut psbs: PtmSetBufferSize = PtmSetBufferSize::new(1024); + // Member type should be Request after initialization + assert_eq!(psbs.get_member_type(), MemberType::Request); + let buf: &[u8] = &[ + 0, 0x12, 0x34, 0x56, 0, 0, 0, 0xA, 0, 0, 0, 0xB, 0, 0, 0, 0xC, + ]; + psbs.update_ptm_with_response(buf)?; + assert_eq!(psbs.get_member_type(), MemberType::Response); + assert_eq!(psbs.get_result_code(), 0x123456); + assert_eq!(psbs.resp.bufsize, 0xA); + assert_eq!(psbs.resp.minsize, 0xB); + assert_eq!(psbs.resp.maxsize, 0xC); + Ok(()) + } +}