|
@@ -1,9 +1,11 @@
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
-use crate::error::{DisplayErr, StringError};
|
|
|
+use crate::{
|
|
|
+ atomic_file::{mask, SyncFile, TryDefault},
|
|
|
+ error::{DisplayErr, StringError},
|
|
|
+};
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
-use btserde::read_from;
|
|
|
use log::error;
|
|
|
use openssl::{
|
|
|
bn::{BigNum, BigNumRef},
|
|
@@ -12,14 +14,12 @@ use openssl::{
|
|
|
use serde::ser;
|
|
|
use std::{
|
|
|
ffi::CStr,
|
|
|
- fs::OpenOptions,
|
|
|
- io::{BufReader, BufWriter, Read, Write},
|
|
|
mem::size_of,
|
|
|
ops::Deref,
|
|
|
- os::{raw::c_char, unix::fs::PermissionsExt},
|
|
|
- path::{Path, PathBuf},
|
|
|
+ os::raw::c_char,
|
|
|
+ path::PathBuf,
|
|
|
str::FromStr,
|
|
|
- sync::{Arc, RwLock, RwLockWriteGuard},
|
|
|
+ sync::{Arc, RwLock},
|
|
|
time::Duration,
|
|
|
};
|
|
|
use tss_esapi::{
|
|
@@ -458,6 +458,30 @@ pub struct ExportedCreds {
|
|
|
writecap: Option<Writecap>,
|
|
|
}
|
|
|
|
|
|
+/// A public/private key pair.
|
|
|
+#[derive(Clone)]
|
|
|
+struct KeyPair<S: Scheme> {
|
|
|
+ public: AsymKeyPub<S>,
|
|
|
+ /// A rust struct which wraps an `ESYS_TR` from the ESAPI.
|
|
|
+ private: KeyHandle,
|
|
|
+}
|
|
|
+
|
|
|
+impl<S: Scheme> KeyPair<S> {
|
|
|
+ fn from_stored(context: &mut Context, stored: &StoredKeyPair<S>) -> Result<KeyPair<S>> {
|
|
|
+ let tpm_handle = TpmHandle::try_from(stored.private)?;
|
|
|
+ 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<S> {
|
|
|
+ let public = self.public.clone();
|
|
|
+ StoredKeyPair { public, private }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// A public/private key pair in a form which can be serialized and deserialized.
|
|
|
#[derive(Serialize, Clone)]
|
|
|
struct StoredKeyPair<S: Scheme> {
|
|
@@ -467,7 +491,7 @@ struct StoredKeyPair<S: Scheme> {
|
|
|
|
|
|
impl<'de, S: Scheme> Deserialize<'de> for StoredKeyPair<S> {
|
|
|
fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
|
|
|
- const FIELDS: &[&str] = &["public", "private", "writecap"];
|
|
|
+ const FIELDS: &[&str] = &["public", "private"];
|
|
|
|
|
|
struct StructVisitor<S: Scheme>(PhantomData<S>);
|
|
|
|
|
@@ -501,6 +525,13 @@ impl<'de, S: Scheme> Deserialize<'de> for StoredKeyPair<S> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Credentials which can be used for signing and encrypting.
|
|
|
+struct CredData {
|
|
|
+ sign: KeyPair<Sign>,
|
|
|
+ enc: KeyPair<Encrypt>,
|
|
|
+ writecap: Option<Writecap>,
|
|
|
+}
|
|
|
+
|
|
|
/// Credentials in a form which can be serialized and deserialized.
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
|
struct StoredCredData {
|
|
@@ -538,46 +569,11 @@ impl Storage {
|
|
|
storage_key: None,
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- fn save<W: Write>(&self, to: &mut W) -> Result<()> {
|
|
|
- Ok(write_to(self, to)?)
|
|
|
- }
|
|
|
-
|
|
|
- fn load<R: Read>(from: &mut R) -> Result<Storage> {
|
|
|
- Ok(read_from(from)?)
|
|
|
- }
|
|
|
-
|
|
|
- fn init<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
|
- let file = OpenOptions::new()
|
|
|
- .write(true)
|
|
|
- .create_new(true)
|
|
|
- .open(&path)?;
|
|
|
- // Only allow access from the current user.
|
|
|
- let metadata = file.metadata()?;
|
|
|
- let mut permissions = metadata.permissions();
|
|
|
- permissions.set_mode(0o600);
|
|
|
- file.set_permissions(permissions)?;
|
|
|
- let mut reader = BufWriter::new(file);
|
|
|
- self.save(&mut reader)?;
|
|
|
- reader.flush()?;
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- 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)
|
|
|
- }
|
|
|
- }
|
|
|
+impl TryDefault for Storage {
|
|
|
+ fn try_default() -> Result<Self> {
|
|
|
+ Ok(Storage::new(Cookie::random()?))
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -774,46 +770,18 @@ impl<'a> KeyBuilder<'a, Encrypt> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// A public/private key pair.
|
|
|
-#[derive(Clone)]
|
|
|
-struct KeyPair<S: Scheme> {
|
|
|
- public: AsymKeyPub<S>,
|
|
|
- /// A rust struct which wraps an `ESYS_TR` from the ESAPI.
|
|
|
- private: KeyHandle,
|
|
|
-}
|
|
|
-
|
|
|
-impl<S: Scheme> KeyPair<S> {
|
|
|
- fn from_stored(context: &mut Context, stored: &StoredKeyPair<S>) -> Result<KeyPair<S>> {
|
|
|
- let tpm_handle = TpmHandle::try_from(stored.private)?;
|
|
|
- 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<S> {
|
|
|
- let public = self.public.clone();
|
|
|
- StoredKeyPair { public, private }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/// Credentials which can be used for signing and encrypting.
|
|
|
-struct CredData {
|
|
|
- sign: KeyPair<Sign>,
|
|
|
- enc: KeyPair<Encrypt>,
|
|
|
- writecap: Option<Writecap>,
|
|
|
-}
|
|
|
-
|
|
|
struct State {
|
|
|
context: Context,
|
|
|
storage: Storage,
|
|
|
+ file: SyncFile<Storage>,
|
|
|
node_creds: Option<TpmCreds>,
|
|
|
storage_key: Option<KeyPair<Encrypt>>,
|
|
|
}
|
|
|
|
|
|
impl State {
|
|
|
- fn new(mut context: Context, storage: Storage) -> Result<State> {
|
|
|
+ fn new(mut context: Context, path: PathBuf) -> Result<State> {
|
|
|
+ let file = SyncFile::new(path, mask::OWNER_ONLY);
|
|
|
+ let storage: Storage = file.load_or_init()?;
|
|
|
let storage_key = match &storage.storage_key {
|
|
|
Some(storage_key) => Some(KeyPair::from_stored(&mut context, storage_key)?),
|
|
|
None => None,
|
|
@@ -821,12 +789,13 @@ impl State {
|
|
|
Ok(State {
|
|
|
context,
|
|
|
storage,
|
|
|
+ file,
|
|
|
node_creds: None,
|
|
|
storage_key,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- fn init_node_creds(&mut self, state: &Arc<RwLock<State>>) -> Result<()> {
|
|
|
+ fn init_node_creds(&mut self, state: Arc<RwLock<State>>) -> Result<()> {
|
|
|
let tpm_handles = match &self.storage.node {
|
|
|
Some(handles) => handles,
|
|
|
None => return Ok(()),
|
|
@@ -840,11 +809,14 @@ impl State {
|
|
|
self.node_creds = Some(TpmCreds::new(key_handles, state));
|
|
|
Ok(())
|
|
|
}
|
|
|
+
|
|
|
+ fn save(&mut self) -> Result<()> {
|
|
|
+ self.file.save(&self.storage)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub struct TpmCredStore {
|
|
|
state: Arc<RwLock<State>>,
|
|
|
- storage_path: PathBuf,
|
|
|
cookie: Cookie,
|
|
|
}
|
|
|
|
|
@@ -861,28 +833,16 @@ impl TpmCredStore {
|
|
|
}
|
|
|
|
|
|
pub fn from_context(mut context: Context, state_path: PathBuf) -> Result<TpmCredStore> {
|
|
|
- let storage = Storage::load_or_init(&state_path)?;
|
|
|
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 state = State::new(context, state_path)?;
|
|
|
+ let cookie = state.storage.cookie.clone();
|
|
|
+ let state = Arc::new(RwLock::new(state));
|
|
|
{
|
|
|
let mut guard = state.write().display_err()?;
|
|
|
- guard.init_node_creds(&state)?;
|
|
|
+ guard.init_node_creds(state.clone())?;
|
|
|
}
|
|
|
- Ok(TpmCredStore {
|
|
|
- state,
|
|
|
- storage_path: state_path,
|
|
|
- cookie,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- fn save_storage(&self, guard: &mut RwLockWriteGuard<State>) -> Result<()> {
|
|
|
- let file = OpenOptions::new().write(true).open(&self.storage_path)?;
|
|
|
- let mut writer = BufWriter::new(file);
|
|
|
- guard.storage.save(&mut writer)?;
|
|
|
- writer.flush()?;
|
|
|
- Ok(())
|
|
|
+ Ok(TpmCredStore { state, cookie })
|
|
|
}
|
|
|
|
|
|
fn persist<F: FnOnce(&mut State, StoredCredData)>(
|
|
@@ -907,7 +867,7 @@ impl TpmCredStore {
|
|
|
writecap: creds.writecap.clone(),
|
|
|
};
|
|
|
update_storage(&mut guard, handles);
|
|
|
- match self.save_storage(&mut guard) {
|
|
|
+ match guard.save() {
|
|
|
Ok(_) => Ok(()),
|
|
|
Err(error) => {
|
|
|
let result = guard
|
|
@@ -950,7 +910,7 @@ impl TpmCredStore {
|
|
|
guard.storage_key = Some(storage_key.clone());
|
|
|
let tpm_handle = guard.context.persist_key(storage_key.private)?;
|
|
|
guard.storage.storage_key = Some(storage_key.to_stored(tpm_handle));
|
|
|
- if let Err(err) = self.save_storage(&mut guard) {
|
|
|
+ if let Err(err) = guard.save() {
|
|
|
guard.storage_key = None;
|
|
|
guard.storage.storage_key = None;
|
|
|
guard
|
|
@@ -990,7 +950,7 @@ impl TpmCredStore {
|
|
|
enc,
|
|
|
writecap: None,
|
|
|
};
|
|
|
- let creds = TpmCreds::new(cred_data, &self.state);
|
|
|
+ let creds = TpmCreds::new(cred_data, self.state.clone());
|
|
|
self.persist(&creds, |state, handles| {
|
|
|
state.storage.node = Some(handles);
|
|
|
state.node_creds = Some(creds.clone());
|
|
@@ -1050,35 +1010,7 @@ impl CredStore for TpmCredStore {
|
|
|
let mut guard = self.state.write().display_err()?;
|
|
|
let key_handles = root_handles.to_cred_data(&mut guard.context)?;
|
|
|
guard.context.set_auth(&key_handles, password)?;
|
|
|
- Ok(TpmCreds::new(key_handles, &self.state))
|
|
|
- }
|
|
|
-
|
|
|
- fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle> {
|
|
|
- {
|
|
|
- let guard = self.state.read().display_err()?;
|
|
|
- if guard.storage.root.is_some() {
|
|
|
- return Err(bterr!("root creds have already been generated"));
|
|
|
- }
|
|
|
- }
|
|
|
- let policy = {
|
|
|
- let mut guard = self.state.write().display_err()?;
|
|
|
- guard.context.dup_with_password_policy()?
|
|
|
- };
|
|
|
- let sign = self.gen_root_sign_key(password, policy.clone())?;
|
|
|
- let enc = self.gen_root_enc_key(password, policy)?;
|
|
|
- let cred_data = CredData {
|
|
|
- sign,
|
|
|
- enc,
|
|
|
- writecap: None,
|
|
|
- };
|
|
|
- {
|
|
|
- let mut guard = self.state.write().display_err()?;
|
|
|
- guard.context.set_auth(&cred_data, password)?;
|
|
|
- }
|
|
|
- let mut creds = TpmCreds::new(cred_data, &self.state);
|
|
|
- creds.init_root_writecap(Epoch::now() + Self::DEFAULT_WRITECAP_EXP)?;
|
|
|
- self.persist(&creds, |state, handles| state.storage.root = Some(handles))?;
|
|
|
- Ok(creds)
|
|
|
+ Ok(TpmCreds::new(key_handles, self.state.clone()))
|
|
|
}
|
|
|
|
|
|
fn storage_key(&self) -> Result<AsymKeyPub<Encrypt>> {
|
|
@@ -1126,7 +1058,9 @@ impl CredStore for TpmCredStore {
|
|
|
writecap: root_creds.writecap.clone(),
|
|
|
})
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
+impl CredStoreMut for TpmCredStore {
|
|
|
fn import_root_creds(&self, password: &str, exported: ExportedCreds) -> Result<TpmCreds> {
|
|
|
let aead_key = exported.params.derive_key(password)?;
|
|
|
let auth = Auth::try_from(password.as_bytes())?;
|
|
@@ -1148,12 +1082,40 @@ impl CredStore for TpmCredStore {
|
|
|
enc,
|
|
|
writecap: exported.writecap,
|
|
|
};
|
|
|
- TpmCreds::new(cred_data, &self.state)
|
|
|
+ TpmCreds::new(cred_data, self.state.clone())
|
|
|
};
|
|
|
self.persist(&creds, |state, handles| state.storage.root = Some(handles))?;
|
|
|
Ok(creds)
|
|
|
}
|
|
|
|
|
|
+ fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle> {
|
|
|
+ {
|
|
|
+ let guard = self.state.read().display_err()?;
|
|
|
+ if guard.storage.root.is_some() {
|
|
|
+ return Err(bterr!("root creds have already been generated"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let policy = {
|
|
|
+ let mut guard = self.state.write().display_err()?;
|
|
|
+ guard.context.dup_with_password_policy()?
|
|
|
+ };
|
|
|
+ let sign = self.gen_root_sign_key(password, policy.clone())?;
|
|
|
+ let enc = self.gen_root_enc_key(password, policy)?;
|
|
|
+ let cred_data = CredData {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ writecap: None,
|
|
|
+ };
|
|
|
+ {
|
|
|
+ let mut guard = self.state.write().display_err()?;
|
|
|
+ guard.context.set_auth(&cred_data, password)?;
|
|
|
+ }
|
|
|
+ let mut creds = TpmCreds::new(cred_data, self.state.clone());
|
|
|
+ creds.init_root_writecap(Epoch::now() + Self::DEFAULT_WRITECAP_EXP)?;
|
|
|
+ self.persist(&creds, |state, handles| state.storage.root = Some(handles))?;
|
|
|
+ Ok(creds)
|
|
|
+ }
|
|
|
+
|
|
|
fn assign_node_writecap(
|
|
|
&self,
|
|
|
handle: &mut Self::CredHandle,
|
|
@@ -1167,7 +1129,7 @@ impl CredStore for TpmCredStore {
|
|
|
if let Some(creds) = state.storage.node.as_mut() {
|
|
|
creds.writecap = Some(writecap);
|
|
|
}
|
|
|
- self.save_storage(&mut state)
|
|
|
+ state.save()
|
|
|
}
|
|
|
|
|
|
fn assign_root_writecap(
|
|
@@ -1180,7 +1142,7 @@ impl CredStore for TpmCredStore {
|
|
|
if let Some(creds) = state.storage.root.as_mut() {
|
|
|
creds.writecap = Some(writecap);
|
|
|
}
|
|
|
- self.save_storage(&mut state)
|
|
|
+ state.save()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1310,11 +1272,11 @@ pub struct TpmCreds {
|
|
|
}
|
|
|
|
|
|
impl TpmCreds {
|
|
|
- fn new(key_handles: CredData, state: &Arc<RwLock<State>>) -> TpmCreds {
|
|
|
+ fn new(key_handles: CredData, state: Arc<RwLock<State>>) -> TpmCreds {
|
|
|
TpmCreds {
|
|
|
sign: key_handles.sign,
|
|
|
enc: key_handles.enc,
|
|
|
- state: state.clone(),
|
|
|
+ state,
|
|
|
writecap: key_handles.writecap,
|
|
|
}
|
|
|
}
|
|
@@ -1520,8 +1482,9 @@ mod test {
|
|
|
use super::*;
|
|
|
|
|
|
use crate::{error::AnyhowErrorExt, test_helpers::BtCursor};
|
|
|
+ use btserde::read_from;
|
|
|
use ctor::ctor;
|
|
|
- use std::{fs::File, io::SeekFrom};
|
|
|
+ use std::{fs::File, io::SeekFrom, os::unix::fs::PermissionsExt};
|
|
|
use swtpm_harness::SwtpmHarness;
|
|
|
use tss_esapi::{
|
|
|
interface_types::ecc::EccCurve,
|