|
@@ -1,16 +1,16 @@
|
|
|
use super::*;
|
|
|
|
|
|
use std::{
|
|
|
- io::{Read, Write},
|
|
|
+ io::{Read, Write, BufReader},
|
|
|
os::{
|
|
|
raw::c_char,
|
|
|
unix::fs::PermissionsExt,
|
|
|
},
|
|
|
ffi::CStr,
|
|
|
- path::Path,
|
|
|
- fs::{File, OpenOptions},
|
|
|
- sync::{Arc, Mutex},
|
|
|
- mem::size_of,
|
|
|
+ path::{Path, PathBuf},
|
|
|
+ fs::{OpenOptions},
|
|
|
+ sync::{Arc, RwLock, RwLockWriteGuard},
|
|
|
+ mem::size_of
|
|
|
};
|
|
|
use openssl::{
|
|
|
bn::BigNum,
|
|
@@ -34,12 +34,14 @@ use tss_esapi::{
|
|
|
},
|
|
|
tcti_ldr::{TctiNameConf, TabrmdConfig},
|
|
|
interface_types::{
|
|
|
- resource_handles::Hierarchy,
|
|
|
+ resource_handles::{Hierarchy, Provision},
|
|
|
algorithm::HashingAlgorithm,
|
|
|
key_bits::RsaKeyBits,
|
|
|
dynamic_handles::Persistent,
|
|
|
+ session_handles::AuthSession,
|
|
|
},
|
|
|
structures::{
|
|
|
+ Auth,
|
|
|
Digest,
|
|
|
HashScheme,
|
|
|
Public,
|
|
@@ -57,9 +59,9 @@ use tss_esapi::{
|
|
|
CapabilityData,
|
|
|
},
|
|
|
attributes::{
|
|
|
- object::ObjectAttributes,
|
|
|
+ object::ObjectAttributes, SessionAttributesBuilder,
|
|
|
},
|
|
|
- handles::{KeyHandle, PersistentTpmHandle},
|
|
|
+ handles::{KeyHandle, PersistentTpmHandle, TpmHandle},
|
|
|
};
|
|
|
|
|
|
impl From<tss_esapi::Error> for Error {
|
|
@@ -95,11 +97,19 @@ trait ContextExt {
|
|
|
.conv_err()?;
|
|
|
Ok(Persistent::Persistent(handle))
|
|
|
}
|
|
|
+
|
|
|
+ fn persist_key(&mut self, key_handle: KeyHandle) -> Result<TPM2_HANDLE>;
|
|
|
+
|
|
|
+ fn evict_key(&mut self, tpm_handle: TPM2_HANDLE, key_handle: Option<KeyHandle>) -> Result<()>;
|
|
|
+
|
|
|
+ fn key_handle(&mut self, tpm_handle: TpmHandle) -> Result<KeyHandle>;
|
|
|
+
|
|
|
+ fn start_default_auth_session(&mut self) -> Result<AuthSession>;
|
|
|
}
|
|
|
|
|
|
impl ContextExt for Context {
|
|
|
fn persistent_handles(&mut self) -> Result<Vec<PersistentTpmHandle>> {
|
|
|
- let capability= CapabilityType::Handles;
|
|
|
+ let capability = CapabilityType::Handles;
|
|
|
let property = TPM2_PERSISTENT_FIRST;
|
|
|
let max = usize::try_from(TPM2_MAX_CAP_BUFFER).unwrap();
|
|
|
let count = (max - size_of::<TPM2_CAP>() - size_of::<u32>()) / size_of::<TPM2_HANDLE>();
|
|
@@ -107,11 +117,13 @@ impl ContextExt for Context {
|
|
|
let mut all_handles = Vec::new();
|
|
|
loop {
|
|
|
let (handles, more) = self.get_capability(capability, property, property_count)
|
|
|
- .unwrap();
|
|
|
+ .conv_err()?;
|
|
|
let list = match handles {
|
|
|
CapabilityData::Handles(list) => list.into_inner(),
|
|
|
- _ => panic!("Unexpected capability type returned by TPM: {:?}", handles),
|
|
|
+ _ => return Err(Error::custom(
|
|
|
+ format!("Unexpected capability type returned by TPM: {:?}", handles))),
|
|
|
};
|
|
|
+ all_handles.reserve(list.len());
|
|
|
for handle in list {
|
|
|
all_handles.push(PersistentTpmHandle::try_from(handle).conv_err()?);
|
|
|
}
|
|
@@ -119,13 +131,79 @@ impl ContextExt for Context {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+ all_handles.sort_unstable_by_key(|handle| TPM2_HANDLE::from(*handle));
|
|
|
Ok(all_handles)
|
|
|
}
|
|
|
+
|
|
|
+ fn persist_key(&mut self, key_handle: KeyHandle) -> Result<TPM2_HANDLE> {
|
|
|
+ let persistent = self.execute_with_session(None, |ctx| ctx.first_free_persistent())?;
|
|
|
+ self.evict_control(Provision::Owner, key_handle.into(), persistent).conv_err()?;
|
|
|
+ let Persistent::Persistent(inner) = persistent;
|
|
|
+ Ok(TPM2_HANDLE::from(inner))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn evict_key(&mut self, persistent: TPM2_HANDLE, key_handle: Option<KeyHandle>) -> Result<()> {
|
|
|
+ let tpm_handle = TpmHandle::try_from(persistent).conv_err()?;
|
|
|
+ let key_handle = match key_handle {
|
|
|
+ Some(key_handle) => key_handle,
|
|
|
+ None => self.tr_from_tpm_public(tpm_handle)?.into()
|
|
|
+ };
|
|
|
+ let persistent = Persistent::Persistent(
|
|
|
+ PersistentTpmHandle::try_from(tpm_handle).conv_err()?);
|
|
|
+ self.evict_control(Provision::Owner, key_handle.into(), persistent)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn start_default_auth_session(&mut self) -> Result<AuthSession> {
|
|
|
+ let session = self.start_auth_session(
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ SessionType::Hmac,
|
|
|
+ SymmetricDefinition::AES_256_CFB,
|
|
|
+ HashingAlgorithm::Sha256,
|
|
|
+ )
|
|
|
+ .conv_err()?
|
|
|
+ .ok_or_else(|| Error::custom("empty session handle received from TPM"))?;
|
|
|
+
|
|
|
+ let (attributes, mask) = SessionAttributesBuilder::new()
|
|
|
+ .with_decrypt(true)
|
|
|
+ .with_encrypt(true)
|
|
|
+ .build();
|
|
|
+ self.tr_sess_set_attributes(session, attributes, mask).conv_err()?;
|
|
|
+
|
|
|
+ Ok(session)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Loads the public information from the persistent object stored in the TPM referred to by
|
|
|
+ /// `tpm_handle` and returns the `KeyHandle` for this object. If the TPM handle refers to an
|
|
|
+ /// object which is not actually a key, then an error is returned.
|
|
|
+ fn key_handle(&mut self, tpm_handle: TpmHandle) -> Result<KeyHandle> {
|
|
|
+ let obj_handle = self.execute_with_session(None, |ctx| {
|
|
|
+ ctx.tr_from_tpm_public(tpm_handle).conv_err()
|
|
|
+ })?;
|
|
|
+ Ok(obj_handle.into())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+trait DigestExt {
|
|
|
+ fn empty() -> Digest;
|
|
|
+}
|
|
|
+
|
|
|
+impl DigestExt for Digest {
|
|
|
+ fn empty() -> Digest {
|
|
|
+ let empty = [0u8; 0];
|
|
|
+ Digest::try_from(empty.as_slice()).unwrap()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const COOKIE_LEN: usize = RSA_KEY_BYTES;
|
|
|
|
|
|
-struct Cookie([u8; COOKIE_LEN]);
|
|
|
+#[derive(Serialize, Deserialize, Clone)]
|
|
|
+struct Cookie(
|
|
|
+ #[serde(with = "BigArray")]
|
|
|
+ [u8; COOKIE_LEN]
|
|
|
+);
|
|
|
|
|
|
impl Cookie {
|
|
|
fn random() -> Result<Cookie> {
|
|
@@ -136,25 +214,6 @@ impl Cookie {
|
|
|
Cookie([0; COOKIE_LEN])
|
|
|
}
|
|
|
|
|
|
- fn load_or_init<P: AsRef<Path>>(cookie_path: P) -> Result<Cookie> {
|
|
|
- let cookie = match OpenOptions::new().read(true).open(&cookie_path) {
|
|
|
- Ok(mut file) => {
|
|
|
- Cookie::load(&mut file)?
|
|
|
- },
|
|
|
- Err(error) => {
|
|
|
- if std::io::ErrorKind::NotFound != error.kind() {
|
|
|
- return Err(Error::from(error));
|
|
|
- }
|
|
|
- let cookie = Cookie::random()?;
|
|
|
- let mut file = OpenOptions::new().write(true).create_new(true).open(&cookie_path)
|
|
|
- .conv_err()?;
|
|
|
- cookie.save(&mut file)?;
|
|
|
- cookie
|
|
|
- }
|
|
|
- };
|
|
|
- Ok(cookie)
|
|
|
- }
|
|
|
-
|
|
|
fn as_slice(&self) -> &[u8] {
|
|
|
self.0.as_slice()
|
|
|
}
|
|
@@ -163,90 +222,303 @@ impl Cookie {
|
|
|
self.0.as_mut_slice()
|
|
|
}
|
|
|
|
|
|
- fn save(&self, to: &mut File) -> Result<()> {
|
|
|
- to.write_all(self.as_slice()).conv_err()?;
|
|
|
- // Only allow read access from the current user.
|
|
|
- let metadata = to.metadata().conv_err()?;
|
|
|
+ /// Returns the `Auth` value associated with this cookie.
|
|
|
+ fn auth(&self) -> Auth {
|
|
|
+ // This shouldn't fail because the given slice is only 64 bytes long.
|
|
|
+ Auth::try_from(&self.as_slice()[..64]).unwrap()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Serialize, Deserialize, Clone)]
|
|
|
+struct StoredKeyPair {
|
|
|
+ public: AsymKeyPub,
|
|
|
+ private: TPM2_HANDLE,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Serialize, Deserialize, Clone)]
|
|
|
+struct TpmHandles {
|
|
|
+ sign: StoredKeyPair,
|
|
|
+ enc: StoredKeyPair,
|
|
|
+}
|
|
|
+
|
|
|
+impl TpmHandles {
|
|
|
+ fn new(sign: StoredKeyPair, enc: StoredKeyPair) -> TpmHandles {
|
|
|
+ TpmHandles { sign, enc }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn to_key_handles(&self, context: &mut Context) -> Result<KeyHandles> {
|
|
|
+ let sign = SignKey(KeyPair::from_stored(context, &self.sign)?);
|
|
|
+ let enc = EncKey(KeyPair::from_stored(context, &self.enc)?);
|
|
|
+ Ok(KeyHandles {sign, enc})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#[derive(Serialize, Deserialize)]
|
|
|
+struct Storage {
|
|
|
+ cookie: Cookie,
|
|
|
+ node: Option<TpmHandles>,
|
|
|
+ root: Option<TpmHandles>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Storage {
|
|
|
+ fn new(cookie: Cookie) -> Storage {
|
|
|
+ Storage {
|
|
|
+ cookie,
|
|
|
+ node: None,
|
|
|
+ root: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn save<W: Write>(&self, to: &mut W) -> Result<()> {
|
|
|
+ write_to(self, to).conv_err()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn load<R: Read>(from: &mut R) -> Result<Storage> {
|
|
|
+ read_from(from).conv_err()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn init<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
|
+ let file = OpenOptions::new().write(true).create_new(true).open(&path)
|
|
|
+ .conv_err()?;
|
|
|
+ // Only allow access from the current user.
|
|
|
+ let metadata = file.metadata().conv_err()?;
|
|
|
let mut permissions = metadata.permissions();
|
|
|
- permissions.set_mode(0o400);
|
|
|
- to.set_permissions(permissions).conv_err()?;
|
|
|
+ permissions.set_mode(0o600);
|
|
|
+ file.set_permissions(permissions).conv_err()?;
|
|
|
+ let mut reader = BufWriter::new(file);
|
|
|
+ self.save(&mut reader)?;
|
|
|
+ reader.flush().conv_err()?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
- fn load(from: &mut File) -> Result<Cookie> {
|
|
|
- let mut cookie = Cookie::empty();
|
|
|
- from.read_exact(cookie.as_mut_slice()).conv_err()?;
|
|
|
- Ok(cookie)
|
|
|
+ fn load_or_init<P: AsRef<Path>>(path: P) -> Result<Storage> {
|
|
|
+ match OpenOptions::new().read(true).open(&path) {
|
|
|
+ Ok(file) => {
|
|
|
+ let mut reader = BufReader::new(file);
|
|
|
+ Self::load(&mut reader)
|
|
|
+ },
|
|
|
+ Err(error) => {
|
|
|
+ if std::io::ErrorKind::NotFound != error.kind() {
|
|
|
+ return Err(error.into());
|
|
|
+ }
|
|
|
+ let state = Self::new(Cookie::random()?);
|
|
|
+ state.init(path)?;
|
|
|
+ Ok(state)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub(crate) struct TpmCredStore {
|
|
|
- context: Arc<Mutex<Context>>,
|
|
|
- cookie: Cookie,
|
|
|
+#[derive(Clone, Copy)]
|
|
|
+enum KeyKind {
|
|
|
+ Sign,
|
|
|
+ Decrypt,
|
|
|
}
|
|
|
|
|
|
-impl TpmCredStore {
|
|
|
+impl KeyKind {
|
|
|
+ fn sign(self) -> bool {
|
|
|
+ match self {
|
|
|
+ KeyKind::Sign => true,
|
|
|
+ KeyKind::Decrypt => false,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn decrypt(self) -> bool {
|
|
|
+ match self {
|
|
|
+ KeyKind::Sign => false,
|
|
|
+ KeyKind::Decrypt => true,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+struct KeyParams<'a> {
|
|
|
+ kind: KeyKind,
|
|
|
+ allow_dup: bool,
|
|
|
+ scheme: RsaScheme,
|
|
|
+ unique: &'a [u8],
|
|
|
+ auth: Option<Auth>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> KeyParams<'a> {
|
|
|
/// The public exponent to use for generated RSA keys.
|
|
|
const RSA_EXPONENT: u32 = 65537; // 2**16 + 1
|
|
|
|
|
|
const RSA_KEY_BITS: RsaKeyBits = RsaKeyBits::Rsa3072;
|
|
|
|
|
|
- pub(crate) fn new<P: AsRef<Path>>(
|
|
|
- mut context: Context, cookie_path: P
|
|
|
- ) -> Result<TpmCredStore> {
|
|
|
- let cookie = Cookie::load_or_init(cookie_path)?;
|
|
|
- let session = context.start_auth_session(
|
|
|
- None,
|
|
|
- None,
|
|
|
- None,
|
|
|
- SessionType::Hmac,
|
|
|
- SymmetricDefinition::AES_256_CFB,
|
|
|
- HashingAlgorithm::Sha256,
|
|
|
- )
|
|
|
- .conv_err()?
|
|
|
- .ok_or_else(|| Error::custom("Received invalid session handle"))?;
|
|
|
- context.set_sessions((Some(session), None, None));
|
|
|
- let context = Arc::new(Mutex::new(context));
|
|
|
- Ok(TpmCredStore { context, cookie })
|
|
|
+ fn with_unique(unique: &'a [u8]) -> KeyParams<'a> {
|
|
|
+ KeyParams {
|
|
|
+ kind: KeyKind::Sign,
|
|
|
+ allow_dup: false,
|
|
|
+ scheme: RsaScheme::Null,
|
|
|
+ unique,
|
|
|
+ auth: None,
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- fn gen_node_creds(&self) -> Result<TpmCreds> {
|
|
|
- let template = {
|
|
|
- let object_attributes = ObjectAttributes::builder()
|
|
|
- .with_fixed_tpm(true)
|
|
|
- .with_fixed_parent(true)
|
|
|
- .with_sensitive_data_origin(true)
|
|
|
- .with_user_with_auth(true)
|
|
|
- .with_decrypt(true)
|
|
|
- .with_sign_encrypt(true)
|
|
|
- .with_restricted(false)
|
|
|
- .build()
|
|
|
- .conv_err()?;
|
|
|
- let name_hashing_algorithm = HashingAlgorithm::Sha256;
|
|
|
- let empty = [0u8; 0];
|
|
|
- let auth_policy = Digest::try_from(empty.as_slice()).conv_err()?;
|
|
|
- let parameters = PublicRsaParameters::new(
|
|
|
- SymmetricDefinitionObject::Null,
|
|
|
- RsaScheme::Null,
|
|
|
- TpmCredStore::RSA_KEY_BITS,
|
|
|
- RsaExponent::try_from(TpmCredStore::RSA_EXPONENT).conv_err()?,
|
|
|
- );
|
|
|
- let unique = PublicKeyRsa::try_from(self.cookie.as_slice())
|
|
|
- .conv_err()?;
|
|
|
- Public::Rsa {
|
|
|
- object_attributes,
|
|
|
- name_hashing_algorithm,
|
|
|
- auth_policy,
|
|
|
- parameters,
|
|
|
- unique,
|
|
|
- }
|
|
|
+ fn with_kind(mut self, kind: KeyKind) -> Self {
|
|
|
+ self.kind = kind;
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ fn with_allow_dup(mut self, allow_dup: bool) -> Self {
|
|
|
+ self.allow_dup = allow_dup;
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ fn with_scheme(mut self, scheme: RsaScheme) -> Self {
|
|
|
+ self.scheme = scheme;
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ fn with_auth(mut self, auth: Auth) -> Self {
|
|
|
+ self.auth = Some(auth);
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ fn template(&self) -> Result<Public> {
|
|
|
+ let object_attributes = ObjectAttributes::builder()
|
|
|
+ .with_fixed_tpm(!self.allow_dup)
|
|
|
+ .with_fixed_parent(!self.allow_dup)
|
|
|
+ .with_sensitive_data_origin(true)
|
|
|
+ .with_user_with_auth(true)
|
|
|
+ .with_decrypt(self.kind.decrypt())
|
|
|
+ .with_sign_encrypt(self.kind.sign())
|
|
|
+ .with_restricted(false)
|
|
|
+ .build()
|
|
|
+ .conv_err()?;
|
|
|
+ let name_hashing_algorithm = HashingAlgorithm::Sha256;
|
|
|
+ let auth_policy = Digest::empty();
|
|
|
+ let parameters = PublicRsaParameters::new(
|
|
|
+ SymmetricDefinitionObject::Null,
|
|
|
+ self.scheme,
|
|
|
+ Self::RSA_KEY_BITS,
|
|
|
+ RsaExponent::try_from(Self::RSA_EXPONENT).conv_err()?,
|
|
|
+ );
|
|
|
+ let unique = PublicKeyRsa::try_from(self.unique)
|
|
|
+ .conv_err()?;
|
|
|
+ let public = Public::Rsa {
|
|
|
+ object_attributes,
|
|
|
+ name_hashing_algorithm,
|
|
|
+ auth_policy,
|
|
|
+ parameters,
|
|
|
+ unique,
|
|
|
+ };
|
|
|
+ Ok(public)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone)]
|
|
|
+struct KeyPair {
|
|
|
+ public: AsymKeyPub,
|
|
|
+ /// A rust struct which wraps an `ESYS_TR` from the ESAPI.
|
|
|
+ private: KeyHandle
|
|
|
+}
|
|
|
+
|
|
|
+impl KeyPair {
|
|
|
+ fn from_stored(context: &mut Context, stored: &StoredKeyPair) -> Result<KeyPair> {
|
|
|
+ let tpm_handle = TpmHandle::try_from(stored.private).conv_err()?;
|
|
|
+ let key_handle = context.key_handle(tpm_handle)?;
|
|
|
+ Ok(KeyPair { public: stored.public.clone(), private: key_handle })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn to_stored(&self, private: TPM2_HANDLE) -> StoredKeyPair {
|
|
|
+ let public = self.public.clone();
|
|
|
+ StoredKeyPair { public, private }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone)]
|
|
|
+struct SignKey(KeyPair);
|
|
|
+
|
|
|
+impl SignKey {
|
|
|
+ fn public(&self) -> &AsymKeyPub {
|
|
|
+ &self.0.public
|
|
|
+ }
|
|
|
+
|
|
|
+ fn private(&self) -> KeyHandle {
|
|
|
+ self.0.private
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone)]
|
|
|
+struct EncKey(KeyPair);
|
|
|
+
|
|
|
+impl EncKey {
|
|
|
+ fn public(&self) -> &AsymKeyPub {
|
|
|
+ &self.0.public
|
|
|
+ }
|
|
|
+
|
|
|
+ fn private(&self) -> KeyHandle {
|
|
|
+ self.0.private
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+struct KeyHandles {
|
|
|
+ sign: SignKey,
|
|
|
+ enc: EncKey,
|
|
|
+}
|
|
|
+
|
|
|
+struct State {
|
|
|
+ context: Context,
|
|
|
+ storage: Storage,
|
|
|
+ node_creds: Option<TpmCreds>,
|
|
|
+}
|
|
|
+
|
|
|
+impl State {
|
|
|
+ fn new(context: Context, storage: Storage) -> State {
|
|
|
+ State { context, storage, node_creds: None }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn init_node_creds(&mut self, state: &Arc<RwLock<State>>) -> Result<()> {
|
|
|
+ let tpm_handles = match &self.storage.node {
|
|
|
+ Some(handles) => handles,
|
|
|
+ None => return Ok(()),
|
|
|
};
|
|
|
+ let key_handles = tpm_handles.to_key_handles(&mut self.context)?;
|
|
|
+ let auth = self.storage.cookie.auth();
|
|
|
+ self.context.tr_set_auth(key_handles.enc.private().into(), auth.clone())?;
|
|
|
+ self.context.tr_set_auth(key_handles.sign.private().into(), auth)?;
|
|
|
+ self.node_creds = Some(TpmCreds::new(key_handles, state));
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub(crate) struct TpmCredStore {
|
|
|
+ state: Arc<RwLock<State>>,
|
|
|
+ storage_path: PathBuf,
|
|
|
+ cookie: Cookie,
|
|
|
+}
|
|
|
+
|
|
|
+impl TpmCredStore {
|
|
|
+ pub(crate) fn new<P: AsRef<Path>>(mut context: Context, state_path: P) -> Result<TpmCredStore> {
|
|
|
+ let storage = Storage::load_or_init(state_path.as_ref())?;
|
|
|
+ let session = context.start_default_auth_session()?;
|
|
|
+ context.set_sessions((Some(session), None, None));
|
|
|
+ let cookie = storage.cookie.clone();
|
|
|
+ let state = Arc::new(RwLock::new(State::new(context, storage)));
|
|
|
+ {
|
|
|
+ let mut guard = state.write().conv_err()?;
|
|
|
+ guard.init_node_creds(&state)?;
|
|
|
+ }
|
|
|
+ Ok(TpmCredStore { state, storage_path: state_path.as_ref().to_owned(), cookie })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn save_storage(&self, guard: &mut RwLockWriteGuard<State>) -> Result<()> {
|
|
|
+ let file = OpenOptions::new().write(true).open(&self.storage_path).conv_err()?;
|
|
|
+ let mut writer = BufWriter::new(file);
|
|
|
+ guard.storage.save(&mut writer)?;
|
|
|
+ writer.flush().conv_err()?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_key(&self, params: KeyParams) -> Result<KeyPair> {
|
|
|
let result = {
|
|
|
- let mut context = self.context.lock().conv_err()?;
|
|
|
- context.create_primary(
|
|
|
+ let mut guard = self.state.write().conv_err()?;
|
|
|
+ guard.context.create_primary(
|
|
|
Hierarchy::Endorsement,
|
|
|
- template,
|
|
|
- None,
|
|
|
+ params.template()?,
|
|
|
+ params.auth,
|
|
|
None,
|
|
|
None,
|
|
|
None,
|
|
@@ -254,8 +526,128 @@ impl TpmCredStore {
|
|
|
.conv_err()?
|
|
|
};
|
|
|
let public = AsymKeyPub::try_from(result.out_public)?;
|
|
|
+ Ok(KeyPair { public, private: result.key_handle })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_node_sign_key(&self) -> Result<SignKey> {
|
|
|
+ let params = KeyParams::with_unique(self.cookie.as_slice())
|
|
|
+ .with_allow_dup(false)
|
|
|
+ .with_kind(KeyKind::Sign)
|
|
|
+ .with_scheme(RsaScheme::Null)
|
|
|
+ .with_auth(self.cookie.auth());
|
|
|
+ Ok(SignKey(self.gen_key(params)?))
|
|
|
+ }
|
|
|
|
|
|
- Ok(TpmCreds { public, handle: result.key_handle , context: self.context.clone() })
|
|
|
+ fn gen_node_enc_key(&self) -> Result<EncKey> {
|
|
|
+ let params = KeyParams::with_unique(self.cookie.as_slice())
|
|
|
+ .with_allow_dup(false)
|
|
|
+ .with_kind(KeyKind::Decrypt)
|
|
|
+ .with_scheme(RsaScheme::RsaEs)
|
|
|
+ .with_auth(self.cookie.auth());
|
|
|
+ Ok(EncKey(self.gen_key(params)?))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn persist<F: FnOnce(&mut Storage, TpmHandles)>(
|
|
|
+ &self, creds: &TpmCreds, update_storage: F
|
|
|
+ ) -> Result<()> {
|
|
|
+ let mut guard = self.state.write().conv_err()?;
|
|
|
+ let sign_handle = guard.context.persist_key(creds.sign.private())?;
|
|
|
+ let enc_handle = match guard.context.persist_key(creds.enc.private()) {
|
|
|
+ Ok(handle) => handle,
|
|
|
+ Err(error) => {
|
|
|
+ guard.context.evict_key(sign_handle, Some(creds.sign.private()))?;
|
|
|
+ return Err(error)
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let handles = TpmHandles::new(
|
|
|
+ creds.sign.0.to_stored(sign_handle),
|
|
|
+ creds.enc.0.to_stored(enc_handle));
|
|
|
+ update_storage(&mut guard.storage, handles);
|
|
|
+ match self.save_storage(&mut guard) {
|
|
|
+ Ok(_) => Ok(()),
|
|
|
+ Err(error) => {
|
|
|
+ let result = guard.context.evict_key(sign_handle, Some(creds.sign.private()));
|
|
|
+ if let Err(error) = result {
|
|
|
+ error!("failed to evict signing key due to error: {:?}", error)
|
|
|
+ }
|
|
|
+ let result = guard.context.evict_key(enc_handle, Some(creds.enc.private()));
|
|
|
+ if let Err(error) = result {
|
|
|
+ error!("failed to evict encryption key due to error: {:?}", error)
|
|
|
+ }
|
|
|
+ Err(error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_node_creds(&self) -> Result<TpmCreds> {
|
|
|
+ let sign = self.gen_node_sign_key()?;
|
|
|
+ let enc = self.gen_node_enc_key()?;
|
|
|
+ let creds = TpmCreds { sign, enc, state: self.state.clone() };
|
|
|
+ self.persist(&creds, |storage, handles| storage.node = Some(handles))?;
|
|
|
+ Ok(creds)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_root_sign_key(&self, password: &str) -> Result<SignKey> {
|
|
|
+ let unique: [u8; COOKIE_LEN] = rand_array()?;
|
|
|
+ let params = KeyParams::with_unique(unique.as_slice())
|
|
|
+ .with_allow_dup(true)
|
|
|
+ .with_kind(KeyKind::Sign)
|
|
|
+ .with_scheme(RsaScheme::Null)
|
|
|
+ .with_auth(Auth::try_from(password.as_bytes()).conv_err()?);
|
|
|
+ Ok(SignKey(self.gen_key(params)?))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_root_enc_key(&self, password: &str) -> Result<EncKey> {
|
|
|
+ let unique: [u8; COOKIE_LEN] = rand_array()?;
|
|
|
+ let params = KeyParams::with_unique(unique.as_slice())
|
|
|
+ .with_allow_dup(true)
|
|
|
+ .with_kind(KeyKind::Decrypt)
|
|
|
+ .with_scheme(RsaScheme::RsaEs)
|
|
|
+ .with_auth(Auth::try_from(password.as_bytes()).conv_err()?);
|
|
|
+ Ok(EncKey(self.gen_key(params)?))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl CredStore for TpmCredStore {
|
|
|
+ type CredHandle = TpmCreds;
|
|
|
+
|
|
|
+ fn node_creds(&self) -> Result<TpmCreds> {
|
|
|
+ {
|
|
|
+ let guard = self.state.read().conv_err()?;
|
|
|
+ if let Some(creds) = &guard.node_creds {
|
|
|
+ return Ok(creds.clone())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self.gen_node_creds()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn root_creds(&self, password: &str) -> Result<Self::CredHandle> {
|
|
|
+ let root_handles = {
|
|
|
+ let guard = self.state.read().conv_err()?;
|
|
|
+ guard.storage.root.as_ref()
|
|
|
+ .ok_or_else(|| Error::custom("root creds have not yet been generated"))?
|
|
|
+ .clone()
|
|
|
+ };
|
|
|
+ let mut guard = self.state.write().conv_err()?;
|
|
|
+ let key_handles = root_handles.to_key_handles(&mut guard.context)?;
|
|
|
+ let auth = Auth::try_from(password.as_bytes()).conv_err()?;
|
|
|
+ guard.context.tr_set_auth(key_handles.sign.private().into(), auth.clone())?;
|
|
|
+ guard.context.tr_set_auth(key_handles.enc.private().into(), auth)?;
|
|
|
+ Ok(TpmCreds::new(key_handles, &self.state))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle> {
|
|
|
+ {
|
|
|
+ let guard = self.state.read().conv_err()?;
|
|
|
+ if guard.storage.root.is_some() {
|
|
|
+ return Err(Error::custom("root creds have already been generated"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let sign = self.gen_root_sign_key(password)?;
|
|
|
+ let enc = self.gen_root_enc_key(password)?;
|
|
|
+ let creds = TpmCreds { sign, enc, state: self.state.clone() };
|
|
|
+ self.persist(&creds, |storage, handles| storage.root = Some(handles))?;
|
|
|
+ Ok(creds)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -350,27 +742,56 @@ impl HashcheckTicketExt for HashcheckTicket {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[derive(Clone)]
|
|
|
pub(crate) struct TpmCreds {
|
|
|
- public: AsymKeyPub,
|
|
|
- context: Arc<Mutex<Context>>,
|
|
|
- handle: KeyHandle,
|
|
|
+ state: Arc<RwLock<State>>,
|
|
|
+ sign: SignKey,
|
|
|
+ enc: EncKey,
|
|
|
+}
|
|
|
+
|
|
|
+impl TpmCreds {
|
|
|
+ fn new(key_handles: KeyHandles, state: &Arc<RwLock<State>>) -> TpmCreds {
|
|
|
+ TpmCreds { sign: key_handles.sign, enc: key_handles.enc, state: state.clone() }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl Owned for TpmCreds {
|
|
|
fn owner_of_kind(&self, kind: HashKind) -> Principal {
|
|
|
- self.public.owner_of_kind(kind)
|
|
|
+ fn hash(creds: &TpmCreds, msg_digest: MessageDigest, mut buf: &mut [u8]) {
|
|
|
+ let digest = {
|
|
|
+ let sign_der = creds.sign.public().to_der().unwrap();
|
|
|
+ let enc_der = creds.enc.public().to_der().unwrap();
|
|
|
+ let mut hasher = Hasher::new(msg_digest).unwrap();
|
|
|
+ hasher.update(sign_der.as_slice()).unwrap();
|
|
|
+ hasher.update(enc_der.as_slice()).unwrap();
|
|
|
+ hasher.finish().unwrap()
|
|
|
+ };
|
|
|
+ buf.write_all(&digest).unwrap();
|
|
|
+ }
|
|
|
+ match kind {
|
|
|
+ HashKind::Sha2_256 => {
|
|
|
+ let mut buf = [0; 32];
|
|
|
+ hash(self, MessageDigest::sha256(), &mut buf);
|
|
|
+ Principal(Hash::Sha2_256(buf))
|
|
|
+ },
|
|
|
+ HashKind::Sha2_512 => {
|
|
|
+ let mut buf = [0; 64];
|
|
|
+ hash(self, MessageDigest::sha512(), &mut buf);
|
|
|
+ Principal(Hash::Sha2_512(buf))
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Verifier for TpmCreds {
|
|
|
fn verify<'a, I: Iterator<Item=&'a [u8]>>(&self, parts: I, signature: &[u8]) -> Result<bool> {
|
|
|
- self.public.verify(parts, signature)
|
|
|
+ self.sign.public().verify(parts, signature)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Encrypter for TpmCreds {
|
|
|
fn encrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
|
|
|
- self.public.encrypt(slice)
|
|
|
+ self.enc.public().encrypt(slice)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -378,7 +799,7 @@ impl CredsPub for TpmCreds {}
|
|
|
|
|
|
impl Signer for TpmCreds {
|
|
|
fn sign<'a, I: Iterator<Item=&'a [u8]>>(&self, parts: I) -> Result<Signature> {
|
|
|
- let msg_digest = self.public.digest();
|
|
|
+ let msg_digest = self.sign.public().digest();
|
|
|
let digest = {
|
|
|
let mut hasher = Hasher::new(msg_digest).conv_err()?;
|
|
|
for part in parts {
|
|
@@ -391,8 +812,8 @@ impl Signer for TpmCreds {
|
|
|
let validation = HashcheckTicket::null();
|
|
|
let scheme = SignatureScheme::RsaSsa { hash_scheme: msg_digest.hash_scheme()? };
|
|
|
let sig = {
|
|
|
- let mut context = self.context.lock().conv_err()?;
|
|
|
- context.sign(self.handle, digest, scheme, validation)
|
|
|
+ let mut guard = self.state.write().conv_err()?;
|
|
|
+ guard.context.sign(self.sign.private(), digest, scheme, validation)
|
|
|
.conv_err()?
|
|
|
};
|
|
|
let buf = match sig {
|
|
@@ -412,12 +833,12 @@ impl Signer for TpmCreds {
|
|
|
impl Decrypter for TpmCreds {
|
|
|
fn decrypt(&self, slice: &[u8]) -> Result<Vec<u8>> {
|
|
|
let cipher_text = PublicKeyRsa::try_from(slice).conv_err()?;
|
|
|
- let in_scheme = RsaDecryptionScheme::RsaEs;
|
|
|
+ let in_scheme = RsaDecryptionScheme::Null;
|
|
|
let empty = [0u8; 0];
|
|
|
let label = Data::try_from(empty.as_slice()).conv_err()?;
|
|
|
let plain_text = {
|
|
|
- let mut lock = self.context.lock().conv_err()?;
|
|
|
- lock.rsa_decrypt(self.handle, cipher_text, in_scheme, label)?
|
|
|
+ let mut guard = self.state.write().conv_err()?;
|
|
|
+ guard.context.rsa_decrypt(self.enc.private(), cipher_text, in_scheme, label)?
|
|
|
};
|
|
|
Ok(Vec::from(plain_text.value()))
|
|
|
}
|
|
@@ -427,7 +848,7 @@ impl CredsPriv for TpmCreds {}
|
|
|
|
|
|
impl Creds for TpmCreds {
|
|
|
fn public(&self) -> &AsymKeyPub {
|
|
|
- &self.public
|
|
|
+ unimplemented!()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -603,7 +1024,7 @@ mod test {
|
|
|
let metadata = cookie.metadata().conv_err()?;
|
|
|
let actual = metadata.permissions().mode();
|
|
|
// Assert that the cookie can only be read by its owner.
|
|
|
- assert_eq!(0o400, 0o777 & actual);
|
|
|
+ assert_eq!(0o600, 0o777 & actual);
|
|
|
drop(store);
|
|
|
dir.close()?;
|
|
|
Ok(())
|
|
@@ -650,34 +1071,53 @@ mod test {
|
|
|
assert_eq(MessageDigest::sha3_512(), Nid::sha3_512());
|
|
|
}
|
|
|
|
|
|
- fn test_store() -> Result<TpmCredStore> {
|
|
|
+ fn state_path<P: AsRef<Path>>(dir: P) -> PathBuf {
|
|
|
+ dir.as_ref().join("state.bt")
|
|
|
+ }
|
|
|
+
|
|
|
+ fn test_store() -> Result<(TpmCredStore, TempDir)> {
|
|
|
let dir = TempDir::new("btnode").conv_err()?;
|
|
|
- let cookie_path = dir.path().join("cookie.bin");
|
|
|
- let store = TpmCredStore::new(Context::for_test()?, &cookie_path)?;
|
|
|
- dir.close()?;
|
|
|
- Ok(store)
|
|
|
+ let state_path = state_path(dir.path());
|
|
|
+ let store = TpmCredStore::new(Context::for_test()?, &state_path)?;
|
|
|
+ Ok((store, dir))
|
|
|
}
|
|
|
|
|
|
- #[test]
|
|
|
- fn tpm_sign_verify() -> Result<()> {
|
|
|
- let store = test_store()?;
|
|
|
- let handle = store.gen_node_creds()?;
|
|
|
+ fn sign_verify_test(creds: &TpmCreds) -> Result<()> {
|
|
|
let data: [u8; 1024] = rand_array()?;
|
|
|
let parts = [data.as_slice()];
|
|
|
- let sig = handle.sign(parts.into_iter())?;
|
|
|
- assert!(handle.verify(parts.into_iter(), sig.as_slice())?);
|
|
|
- Ok(())
|
|
|
+ let sig = creds.sign(parts.into_iter())?;
|
|
|
+ if creds.verify(parts.into_iter(), sig.as_slice())? {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ Err(Error::custom("signature verification failed"))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
- fn tpm_encrypt_decrypt() -> Result<()> {
|
|
|
- let store = test_store()?;
|
|
|
- let handle = store.gen_node_creds()?;
|
|
|
+ fn tpm_sign_verify() -> Result<()> {
|
|
|
+ let (store, _dir) = test_store()?;
|
|
|
+ let creds = store.gen_node_creds()?;
|
|
|
+ sign_verify_test(&creds)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn encrypt_decrypt_test(creds: &TpmCreds) -> Result<()> {
|
|
|
let expected: [u8; RSA_KEY_BYTES / 2] = rand_array()?;
|
|
|
- let ct = handle.encrypt(expected.as_slice())?;
|
|
|
- let actual = handle.decrypt(&ct)?;
|
|
|
- assert_eq!(expected.as_slice(), actual);
|
|
|
- Ok(())
|
|
|
+ let ct = creds.encrypt(expected.as_slice())?;
|
|
|
+ let actual = creds.decrypt(&ct)?;
|
|
|
+ if expected.as_slice() == actual {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ Err(Error::custom("decrypted data did not match input"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn tpm_encrypt_decrypt() -> Result<()> {
|
|
|
+ let (store, _dir) = test_store()?;
|
|
|
+ let creds = store.gen_node_creds()?;
|
|
|
+ encrypt_decrypt_test(&creds)
|
|
|
}
|
|
|
|
|
|
/// Tests that `HashcheckTicket::null` doesn't panic.
|
|
@@ -689,7 +1129,7 @@ mod test {
|
|
|
/// Checks that the value of `TpmCredStore::RSA_KEY_BITS` matches the value of `RSA_KEY_BYTES`.
|
|
|
#[test]
|
|
|
fn rsa_key_bits_and_key_bytes_compatible() {
|
|
|
- let bytes = match TpmCredStore::RSA_KEY_BITS {
|
|
|
+ let bytes = match KeyParams::RSA_KEY_BITS {
|
|
|
RsaKeyBits::Rsa1024 => 128,
|
|
|
RsaKeyBits::Rsa2048 => 256,
|
|
|
RsaKeyBits::Rsa3072 => 384,
|
|
@@ -698,11 +1138,76 @@ mod test {
|
|
|
assert_eq!(RSA_KEY_BYTES, bytes)
|
|
|
}
|
|
|
|
|
|
- fn list_persistent_handles() {
|
|
|
+ #[test]
|
|
|
+ fn persistent_handles() {
|
|
|
let mut context = Context::for_test().unwrap();
|
|
|
- let all_handles = context.persistent_handles().unwrap();
|
|
|
- for handle in all_handles.iter() {
|
|
|
- println!("{:?}", handle);
|
|
|
- }
|
|
|
+ let _all_handles = context.persistent_handles().unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn first_free_persistent() -> Result<()> {
|
|
|
+ let mut context = Context::for_test()?;
|
|
|
+ let _first = context.first_free_persistent()?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn persist_key() -> Result<()> {
|
|
|
+ let (store, _dir) = test_store()?;
|
|
|
+ let cookie = Cookie::random()?;
|
|
|
+ let params = KeyParams::with_unique(cookie.as_slice());
|
|
|
+ let pair = store.gen_key(params)?;
|
|
|
+ let mut guard = store.state.write().conv_err()?;
|
|
|
+ guard.context.persist_key(pair.private)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Tests that the same node key is returned by after a cred store is dropped and recreated.
|
|
|
+ #[test]
|
|
|
+ fn node_key_persisted() -> Result<()> {
|
|
|
+ let (store, dir) = test_store()?;
|
|
|
+ let expected = {
|
|
|
+ let creds = store.node_creds()?;
|
|
|
+ // This contains a hash of the public keys in `creds`, so it strongly identifies them.
|
|
|
+ creds.owner()
|
|
|
+ };
|
|
|
+ drop(store);
|
|
|
+ let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
|
|
|
+ let creds = store.node_creds()?;
|
|
|
+ let actual = creds.owner();
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ sign_verify_test(&creds)?;
|
|
|
+ encrypt_decrypt_test(&creds)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn root_key_persisted() -> Result<()> {
|
|
|
+ const PASSWORD: &str = "Scaramouch";
|
|
|
+ let (store, dir) = test_store()?;
|
|
|
+ let expected = {
|
|
|
+ let creds = store.gen_root_creds(PASSWORD)?;
|
|
|
+ creds.owner()
|
|
|
+ };
|
|
|
+ drop(store);
|
|
|
+ let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
|
|
|
+ let creds = store.root_creds(PASSWORD)?;
|
|
|
+ let actual = creds.owner();
|
|
|
+ assert_eq!(expected, actual);
|
|
|
+ sign_verify_test(&creds)?;
|
|
|
+ encrypt_decrypt_test(&creds)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn root_key_not_returned_when_password_wrong() -> Result<()> {
|
|
|
+ let (store, dir) = test_store()?;
|
|
|
+ store.gen_root_creds("Galileo")?;
|
|
|
+ drop(store);
|
|
|
+ let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
|
|
|
+ let creds = store.root_creds("Figaro")?;
|
|
|
+ assert!(sign_verify_test(&creds).is_err());
|
|
|
+ assert!(encrypt_decrypt_test(&creds).is_err());
|
|
|
+ Ok(())
|
|
|
}
|
|
|
}
|