Skip to content

Commit

Permalink
vtpm: Create and cache the TPM endorsement key
Browse files Browse the repository at this point in the history
The EK public area is provided in the Attestation Runtime Protocol. Since the
same EK can be regenerated multiple times, we cache its public area at boot
time to avoid issues like TPM out-of-memory at runtime.

Signed-off-by: Claudio Carvalho <[email protected]>
  • Loading branch information
cclaudio committed Dec 7, 2023
1 parent b9ccf19 commit d3fb0bb
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 3 deletions.
4 changes: 2 additions & 2 deletions libvtpm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ $(MSTPM_MAKEFILE):

# bindings.rs
BINDGEN = ${HOME}/.cargo/bin/bindgen
BINDGEN_FLAGS = --use-core
CLANG_FLAGS = -Wno-incompatible-library-redeclaration
BINDGEN_FLAGS = --use-core --impl-debug
CLANG_FLAGS = -Wno-incompatible-library-redeclaration -I$(MSTPM_DIR) -I$(MSTPM_DIR)/tpm/include/prototypes -I$(MSTPM_DIR)/Platform/include/prototypes -I$(LIBCRT_DIR)/include -I $(OPENSSL_DIR)/include -nostdinc

$(BINDGEN):
cargo install bindgen-cli
Expand Down
11 changes: 10 additions & 1 deletion libvtpm/libvtpm.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ void _plat__NVDisable(int delete);
int _plat__NVEnable(void *platParameter);

int TPM_Manufacture(int firstTime);
int TPM_TearDown(void);
int TPM_TearDown(void);

#include <stdint.h>

#include <tpm/include/Tpm.h>
#include <tpm/include/TpmTypes.h>

#include <tpm/include/prototypes/CreatePrimary_fp.h>
#include <tpm/include/prototypes/FlushContext_fp.h>
#include <tpm/include/prototypes/Marshal_fp.h>
202 changes: 202 additions & 0 deletions src/vtpm/cmds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (C) 2023 IBM
//
// Authors: Claudio Carvalho <[email protected]>

extern crate alloc;

use core::mem::size_of;

use super::{
bindings::{
CreatePrimary_In, CreatePrimary_Out, FlushContext_In, TPM2_CreatePrimary,
TPM2_FlushContext, TPMT_PUBLIC_Marshal, TPMT_PUBLIC,
},
VtpmError,
};
use alloc::{
alloc::{alloc_zeroed, handle_alloc_error, Layout},
boxed::Box,
vec::Vec,
};
use bitflags::bitflags;

const TPM_RH_ENDORSEMENT: u32 = 0x4000_000b;

const TPM_ALG_ECC: u16 = 0x0023;
const TPM_ALG_AES: u16 = 0x0006;
const TPM_ALG_SHA256: u16 = 0x000b;
const TPM_ALG_NULL: u16 = 0x0010;
const TPM_ALG_CFB: u16 = 0x0043;

const TPM_ECC_NIST_P256: u16 = 0x0003;

const TPM_RC_SUCCESS: u32 = 0x0000_0000;

bitflags! {
/// Reserved bits must be zero: [0, 3, 8-9, 12-15, 20-32]
pub struct TpmaObject: u32 {
const FixedTpm = 1 << 1;
const StClear = 1 << 2;
const FixedParent = 1 << 4;
const SensitiveDataOrigin = 1 << 5;
const UserWithAuth = 1 << 6;
const AdminWithPolicy = 1 << 7;
const NoDa = 1 << 10;
const EncryptedDuplication = 1 << 11;
const Restricted = 1 << 16;
const Decrypt = 1 << 17;
const SignEncrypt = 1 << 18;
const X509Sign = 1 << 19;
}
}

/// Create a primary rsa2048:aes128cfb key handle
pub fn vtpm_create_primary_rsa2048(
authpolicy: &[u8],
attributes: TpmaObject,
) -> Result<Box<CreatePrimary_Out>, VtpmError> {
const TPM_ALG_RSA: u16 = 0x0001;
// The CreatePrimary_In and CreatePrimary_Out structures are relatively big
// to be allocated from stack, 608 and 760 bytes respectively.
let mut create_out: Box<CreatePrimary_Out> = unsafe {
let layout = Layout::new::<CreatePrimary_Out>();
let addr = alloc_zeroed(layout);
if addr.is_null() {
handle_alloc_error(layout);
}
Box::from_raw(addr.cast::<CreatePrimary_Out>())
};
let mut create_in: Box<CreatePrimary_In> = unsafe {
let layout = Layout::new::<CreatePrimary_In>();
let addr = alloc_zeroed(layout);
if addr.is_null() {
handle_alloc_error(layout)
}
Box::from_raw(addr.cast::<CreatePrimary_In>())
};

create_in.primaryHandle = TPM_RH_ENDORSEMENT;
create_in.inSensitive.sensitive.userAuth.t.size = 0;
create_in.inSensitive.sensitive.data.t.size = 0;
create_in.inPublic.size = size_of::<TPMT_PUBLIC>() as u16;
create_in.inPublic.publicArea.type_ = TPM_ALG_RSA;
create_in.inPublic.publicArea.nameAlg = TPM_ALG_SHA256;
create_in.inPublic.publicArea.objectAttributes = attributes.bits();
// Union read access is unsafe in Rust. authPolicy is a union.
unsafe {
let authpolicy_in = &mut create_in.inPublic.publicArea.authPolicy;
authpolicy_in.t.size = authpolicy.len() as u16;
authpolicy_in
.t
.buffer
.get_mut(..authpolicy.len())
.ok_or_else(|| VtpmError::Rc(111))?
.copy_from_slice(&authpolicy);
}
let rsa_detail = unsafe { &mut create_in.inPublic.publicArea.parameters.rsaDetail };
rsa_detail.symmetric.algorithm = TPM_ALG_AES;
rsa_detail.symmetric.keyBits.aes = 128_u16;
rsa_detail.symmetric.mode.aes = TPM_ALG_CFB;
rsa_detail.scheme.scheme = TPM_ALG_NULL;
rsa_detail.keyBits = 2048_u16;
rsa_detail.exponent = 0;
// The nonce (t.buffer) is already initialized with zeroes
create_in.inPublic.publicArea.unique.rsa.t.size = 256_u16;
create_in.outsideInfo.t.size = 0;
create_in.creationPCR.count = 0;

match unsafe { TPM2_CreatePrimary(create_in.as_mut(), create_out.as_mut()) } {
TPM_RC_SUCCESS => Ok(create_out),
tpm_rc => Err(VtpmError::Rc(tpm_rc)),
}
}

/// Create a primary ECC key handle
/// ECC NISTP-256 bit key
pub fn vtpm_create_primary_ecc_p256(
authpolicy: &[u8],
attributes: TpmaObject,
) -> Result<Box<CreatePrimary_Out>, VtpmError> {
// The CreatePrimary_In and CreatePrimary_Out structures are relatively big
// to be allocated from stack, 608 and 760 bytes respectively.
let mut create_out: Box<CreatePrimary_Out> = unsafe {
let layout = Layout::new::<CreatePrimary_Out>();
let addr = alloc_zeroed(layout);
if addr.is_null() {
handle_alloc_error(layout)
}
Box::from_raw(addr.cast::<CreatePrimary_Out>())
};
let mut create_in: Box<CreatePrimary_In> = unsafe {
let layout = Layout::new::<CreatePrimary_In>();
let addr = alloc_zeroed(layout);
if addr.is_null() {
handle_alloc_error(layout);
}
Box::from_raw(addr.cast::<CreatePrimary_In>())
};

create_in.primaryHandle = TPM_RH_ENDORSEMENT;
create_in.inSensitive.sensitive.userAuth.t.size = 0;
create_in.inSensitive.sensitive.data.t.size = 0;
create_in.inPublic.size = size_of::<TPMT_PUBLIC>() as u16;
create_in.inPublic.publicArea.type_ = TPM_ALG_ECC;
create_in.inPublic.publicArea.nameAlg = TPM_ALG_SHA256;
create_in.inPublic.publicArea.objectAttributes = attributes.bits();
// Union read access is unsafe in Rust. authPolicy is a union.
unsafe {
let authpolicy_in = &mut create_in.inPublic.publicArea.authPolicy;
authpolicy_in.t.size = authpolicy.len() as u16;
authpolicy_in
.t
.buffer
.get_mut(..authpolicy.len())
.ok_or_else(|| VtpmError::Rc(111))?
.copy_from_slice(&authpolicy);
}
let ecc_detail = unsafe { &mut create_in.inPublic.publicArea.parameters.eccDetail };
ecc_detail.curveID = TPM_ECC_NIST_P256;
ecc_detail.symmetric.algorithm = TPM_ALG_AES;
ecc_detail.symmetric.keyBits.aes = 128_u16;
ecc_detail.symmetric.mode.aes = TPM_ALG_CFB;
ecc_detail.scheme.scheme = TPM_ALG_NULL;
ecc_detail.kdf.scheme = TPM_ALG_NULL;
// The nonce (x and y buffers) is already initialized with zeroes
create_in.inPublic.publicArea.unique.ecc.x.t.size = 32;
create_in.inPublic.publicArea.unique.ecc.y.t.size = 32;
create_in.outsideInfo.t.size = 0;
create_in.creationPCR.count = 0;

match unsafe { TPM2_CreatePrimary(create_in.as_mut(), create_out.as_mut()) } {
TPM_RC_SUCCESS => Ok(create_out),
tpm_rc => Err(VtpmError::Rc(tpm_rc)),
}
}

/// Flush handle context from the TPM memory
pub fn vtpm_flush_context(handle: u32) -> Result<(), VtpmError> {
let mut flush_in = FlushContext_In {
flushHandle: handle,
};

match unsafe { TPM2_FlushContext(&mut flush_in) } {
TPM_RC_SUCCESS => Ok(()),
tpm_rc => Err(VtpmError::Rc(tpm_rc)),
}
}

/// Marshal a TPMT_PUBLIC structure for transmission.
pub fn vtpm_tpmt_public_marshal(src: *mut TPMT_PUBLIC) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(size_of::<TPMT_PUBLIC>());
let mut buf_p = buf.as_mut_ptr();
let buf_pp = &mut buf_p;

unsafe {
let size = TPMT_PUBLIC_Marshal(src, buf_pp, core::ptr::null_mut::<i32>());
buf.set_len(size.into());
};

buf
}
91 changes: 91 additions & 0 deletions src/vtpm/ek.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (C) 2023 IBM
//
// Authors: Claudio Carvalho <[email protected]>

extern crate alloc;

use alloc::{boxed::Box, vec::Vec};
use core::cell::OnceCell;

use crate::{locking::SpinLock, vtpm::cmds::vtpm_tpmt_public_marshal};

use super::{
bindings::CreatePrimary_Out,
cmds::{vtpm_create_primary_rsa2048, vtpm_flush_context, TpmaObject},
VtpmError,
};

/// The EK public area is provided in the Attestation Runtime Protocol. Since the same
/// EK can be regenerated multiple times, we cache its public area at boot time to avoid
/// issues like TPM out-of-memory at runtime.
static TPM_EK: SpinLock<OnceCell<TpmEndorsementKey>> = SpinLock::new(OnceCell::new());

/// Public Area (TPMT_PUBLIC) of the Endorsement Key (EK).
#[derive(Debug)]
pub struct TpmEndorsementKey {
/// Marshaled TPMT_PUBLIC
public_area: Vec<u8>,
}

impl TpmEndorsementKey {
/// Create an EK transient object and return its TPMT_PUBLIC public area.
/// The transient object is flushed from TPM memory
pub fn create() -> Result<TpmEndorsementKey, VtpmError> {
// Policy A sha256: rsa2048:aes128cfb
let authpolicy: [u8; 32] = [
0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d, 0x46, 0xa5,
0xd7, 0x24, 0xfd, 0x52, 0xd7, 0x6e, 0x06, 0x52, 0x0b, 0x64, 0xf2, 0xa1, 0xda, 0x1b,
0x33, 0x14, 0x69, 0xaa,
];

let attributes = TpmaObject::FixedTpm
| TpmaObject::FixedParent
| TpmaObject::SensitiveDataOrigin
| TpmaObject::AdminWithPolicy
| TpmaObject::Restricted
| TpmaObject::Decrypt;

// TODO: Replace the rsa2048 key by the ecc-p256 when the TPM_RC_NO_RESULT (0x154) is fixed.
// This is likely to be related to the ECC issue we are having in the TPM Selftests, which we
// workaround by setting "-DSELF_TEST=NO"
let mut create_out: Box<CreatePrimary_Out> =
vtpm_create_primary_rsa2048(authpolicy.as_slice(), attributes)?;
// let mut create_out: Box<CreatePrimary_Out> = super::cmds::vtpm_create_primary_ecc_p256(authpolicy.as_slice(), attributes)?;

// EK template is well known, see the TCG EK Credential Profile specification.
// With the same Endorsement Primary Seed (EPS), the same EK can be regenerated
// multiple times, as long as the same template (or attributes) is provided.
if let Err(e) = vtpm_flush_context(create_out.objectHandle) {
log::warn!(
"Failed to flush the TPM EK transient handle {:#x}, e = {:#x?}",
create_out.objectHandle,
e
);
}

let marshaled_ekpub = vtpm_tpmt_public_marshal(&mut create_out.outPublic.publicArea);

Ok(TpmEndorsementKey {
public_area: marshaled_ekpub,
})
}

pub fn get_public_area(&self) -> Vec<u8> {
self.public_area.clone()
}
}

pub fn create_tpm_ek() {
let _ = TPM_EK
.lock()
.get_or_init(|| match TpmEndorsementKey::create() {
Ok(ek) => ek,
Err(e) => panic!("Failed to create the TPM Endorsment Key {:#x?}", e),
});
}

pub fn get_tpm_ek() -> Vec<u8> {
TPM_EK.lock().get().unwrap().get_public_area()
}
9 changes: 9 additions & 0 deletions src/vtpm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@

/// C bindings
pub mod bindings;
/// TPM Commands
pub mod cmds;
/// TPM Endorsement key
pub mod ek;
/// MS Simulator functions
pub mod mssim;
/// MS TPM2 functions
pub mod mstpm;
/// Wrappers called in C
pub mod wrapper;

#[derive(Debug, Copy, Clone)]
pub enum VtpmError {
Rc(u32),
}
4 changes: 4 additions & 0 deletions src/vtpm/mssim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::vtpm::bindings::{
_plat__LocalitySet, _plat__NVDisable, _plat__NVEnable, _plat__RunCommand, _plat__SetNvAvail,
_plat__Signal_PowerOn, _plat__Signal_Reset,
};
use crate::vtpm::ek::{create_tpm_ek, get_tpm_ek};
use crate::vtpm::mstpm::{mstpm_manufacture, mstpm_teardown};

pub const TPM_BUFFER_MAX_SIZE: usize = PAGE_SIZE;
Expand Down Expand Up @@ -164,6 +165,9 @@ pub fn mssim_vtpm_init() -> Result<(), SvsmReqError> {

log::info!("vTPM manufactured");

create_tpm_ek();
log::info!("Marshaled EK pub: {:0>2x?}", get_tpm_ek());

Ok(())
}

Expand Down

0 comments on commit d3fb0bb

Please sign in to comment.