|
@@ -18,6 +18,7 @@ use std::{
|
|
|
os::{raw::c_char, unix::fs::PermissionsExt},
|
|
|
path::{Path, PathBuf},
|
|
|
sync::{Arc, RwLock, RwLockWriteGuard},
|
|
|
+ time::Duration,
|
|
|
};
|
|
|
use tss_esapi::{
|
|
|
attributes::{object::ObjectAttributes, SessionAttributesBuilder},
|
|
@@ -104,6 +105,8 @@ trait ContextExt {
|
|
|
auth: Auth,
|
|
|
encryption_key: &AeadKey,
|
|
|
) -> Result<KeyPair<S>>;
|
|
|
+
|
|
|
+ fn set_auth(&mut self, cred_data: &CredData, password: &str) -> Result<()>;
|
|
|
}
|
|
|
|
|
|
impl ContextExt for Context {
|
|
@@ -337,6 +340,13 @@ impl ContextExt for Context {
|
|
|
private: key_handle,
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+ fn set_auth(&mut self, creds: &CredData, password: &str) -> Result<()> {
|
|
|
+ let auth = Auth::try_from(password.as_bytes())?;
|
|
|
+ self.tr_set_auth(creds.sign.private.into(), auth.clone())?;
|
|
|
+ self.tr_set_auth(creds.enc.private.into(), auth)?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
enum IsTrial {
|
|
@@ -447,7 +457,8 @@ fn deserialize_as_vec<'de, D: Deserializer<'de>, T: TryFrom<Vec<u8>>>(
|
|
|
.map_err(|_| de::Error::custom("Unable to convert from vector"))
|
|
|
}
|
|
|
|
|
|
-pub struct ExportedKeyPair<S: Scheme> {
|
|
|
+#[derive(Serialize, Deserialize)]
|
|
|
+pub struct ExportedKeyPair<S> {
|
|
|
scheme: S,
|
|
|
tagged_ct: TaggedCiphertext<TpmBlobs, PublicWrapper>,
|
|
|
}
|
|
@@ -456,59 +467,12 @@ impl<S: Scheme> ExportedKeyPair<S> {
|
|
|
const FIELDS: &'static [&'static str] = &["scheme", "tagged_ct"];
|
|
|
}
|
|
|
|
|
|
-impl<S: Scheme> Serialize for ExportedKeyPair<S> {
|
|
|
- fn serialize<T: Serializer>(&self, ser: T) -> std::result::Result<T::Ok, T::Error> {
|
|
|
- let mut struct_ser = ser.serialize_struct(stringify!(ExportedKey), Self::FIELDS.len())?;
|
|
|
- struct_ser.serialize_field(Self::FIELDS[0], &self.scheme)?;
|
|
|
- struct_ser.serialize_field(Self::FIELDS[1], &self.tagged_ct)?;
|
|
|
- struct_ser.end()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<'de, S: Scheme> Deserialize<'de> for ExportedKeyPair<S> {
|
|
|
- fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
|
|
|
- struct StructVisitor<S: Scheme>(PhantomData<S>);
|
|
|
-
|
|
|
- impl<'de, S: Scheme> Visitor<'de> for StructVisitor<S> {
|
|
|
- type Value = ExportedKeyPair<S>;
|
|
|
-
|
|
|
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
|
- formatter.write_fmt(format_args!("struct {}", stringify!(ExportedKey)))
|
|
|
- }
|
|
|
-
|
|
|
- fn visit_seq<V: SeqAccess<'de>>(
|
|
|
- self,
|
|
|
- mut seq: V,
|
|
|
- ) -> std::result::Result<Self::Value, V::Error> {
|
|
|
- fn conv_err<T, E: de::Error>(
|
|
|
- result: std::result::Result<T, tss_esapi::Error>,
|
|
|
- ) -> std::result::Result<T, E> {
|
|
|
- result.map_err(de::Error::custom)
|
|
|
- }
|
|
|
-
|
|
|
- let scheme: S = seq
|
|
|
- .next_element()?
|
|
|
- .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[0]))?;
|
|
|
- let tagged_ct: TaggedCiphertext<TpmBlobs, PublicWrapper> = seq
|
|
|
- .next_element()?
|
|
|
- .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[1]))?;
|
|
|
- Ok(ExportedKeyPair { scheme, tagged_ct })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- de.deserialize_struct(
|
|
|
- stringify!(ExportedKey),
|
|
|
- Self::FIELDS,
|
|
|
- StructVisitor(PhantomData),
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
pub struct ExportedCreds {
|
|
|
sign: ExportedKeyPair<Sign>,
|
|
|
enc: ExportedKeyPair<Encrypt>,
|
|
|
params: DerivationParams,
|
|
|
+ writecap: Option<Writecap>,
|
|
|
}
|
|
|
|
|
|
/// A public/private key pair in a form which can be serialized and deserialized.
|
|
@@ -518,11 +482,9 @@ struct StoredKeyPair<S: Scheme> {
|
|
|
private: TPM2_HANDLE,
|
|
|
}
|
|
|
|
|
|
-// I was unable to use `derive(Deserialize)` on `StoredKeyPair`, so I resorted to implementing it
|
|
|
-// manually.
|
|
|
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"];
|
|
|
+ const FIELDS: &[&str] = &["public", "private", "writecap"];
|
|
|
|
|
|
struct StructVisitor<S: Scheme>(PhantomData<S>);
|
|
|
|
|
@@ -561,13 +523,18 @@ impl<'de, S: Scheme> Deserialize<'de> for StoredKeyPair<S> {
|
|
|
struct StoredCredData {
|
|
|
sign: StoredKeyPair<Sign>,
|
|
|
enc: StoredKeyPair<Encrypt>,
|
|
|
+ writecap: Option<Writecap>,
|
|
|
}
|
|
|
|
|
|
impl StoredCredData {
|
|
|
fn to_cred_data(&self, context: &mut Context) -> Result<CredData> {
|
|
|
let sign = KeyPair::from_stored(context, &self.sign)?;
|
|
|
let enc = KeyPair::from_stored(context, &self.enc)?;
|
|
|
- Ok(CredData { sign, enc })
|
|
|
+ Ok(CredData {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ writecap: self.writecap.clone(),
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -856,6 +823,7 @@ impl<S: Scheme> KeyPair<S> {
|
|
|
struct CredData {
|
|
|
sign: KeyPair<Sign>,
|
|
|
enc: KeyPair<Encrypt>,
|
|
|
+ writecap: Option<Writecap>,
|
|
|
}
|
|
|
|
|
|
struct State {
|
|
@@ -904,6 +872,7 @@ pub(crate) struct TpmCredStore {
|
|
|
impl TpmCredStore {
|
|
|
const SIGN_SCHEME: Sign = Sign::RSA_PSS_2048_SHA_256;
|
|
|
const ENCRYPT_SCHEME: Encrypt = Encrypt::RSA_OAEP_2048_SHA_256;
|
|
|
+ const DEFAULT_WRITECAP_EXP: Duration = Duration::from_secs(60 * 60 * 24 * 365 * 10);
|
|
|
|
|
|
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())?;
|
|
@@ -949,6 +918,7 @@ impl TpmCredStore {
|
|
|
let handles = StoredCredData {
|
|
|
sign: creds.sign.to_stored(sign_handle),
|
|
|
enc: creds.enc.to_stored(enc_handle),
|
|
|
+ writecap: creds.writecap.clone(),
|
|
|
};
|
|
|
update_storage(&mut guard.storage, handles);
|
|
|
match self.save_storage(&mut guard) {
|
|
@@ -1029,7 +999,11 @@ impl TpmCredStore {
|
|
|
fn gen_node_creds(&self) -> Result<TpmCreds> {
|
|
|
let sign = self.gen_node_sign_key()?;
|
|
|
let enc = self.gen_node_enc_key()?;
|
|
|
- let cred_data = CredData { sign, enc };
|
|
|
+ let cred_data = CredData {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ writecap: None,
|
|
|
+ };
|
|
|
let creds = TpmCreds::new(cred_data, &self.state);
|
|
|
self.persist(&creds, |storage, handles| storage.node = Some(handles))?;
|
|
|
Ok(creds)
|
|
@@ -1100,28 +1074,9 @@ impl DerivationParams {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[derive(Serialize, Deserialize)]
|
|
|
-struct WritecapReqBody {
|
|
|
- pub_key: AsymKeyPub<Sign>,
|
|
|
- req_for: Principal,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Serialize, Deserialize)]
|
|
|
-pub struct WritecapReq {
|
|
|
- body: WritecapReqBody,
|
|
|
- sig: Signature,
|
|
|
-}
|
|
|
-
|
|
|
-impl AsRef<AsymKeyPub<Sign>> for WritecapReq {
|
|
|
- fn as_ref(&self) -> &AsymKeyPub<Sign> {
|
|
|
- &self.body.pub_key
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
impl CredStore for TpmCredStore {
|
|
|
type CredHandle = TpmCreds;
|
|
|
type ExportedCreds = ExportedCreds;
|
|
|
- type WritecapReq = WritecapReq;
|
|
|
|
|
|
fn node_creds(&self) -> Result<TpmCreds> {
|
|
|
{
|
|
@@ -1145,13 +1100,7 @@ impl CredStore for TpmCredStore {
|
|
|
};
|
|
|
let mut guard = self.state.write()?;
|
|
|
let key_handles = root_handles.to_cred_data(&mut guard.context)?;
|
|
|
- let auth = Auth::try_from(password.as_bytes())?;
|
|
|
- guard
|
|
|
- .context
|
|
|
- .tr_set_auth(key_handles.sign.private.into(), auth.clone())?;
|
|
|
- guard
|
|
|
- .context
|
|
|
- .tr_set_auth(key_handles.enc.private.into(), auth)?;
|
|
|
+ guard.context.set_auth(&key_handles, password)?;
|
|
|
Ok(TpmCreds::new(key_handles, &self.state))
|
|
|
}
|
|
|
|
|
@@ -1168,8 +1117,17 @@ impl CredStore for TpmCredStore {
|
|
|
};
|
|
|
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 };
|
|
|
- let creds = TpmCreds::new(cred_data, &self.state);
|
|
|
+ let cred_data = CredData {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ writecap: None,
|
|
|
+ };
|
|
|
+ {
|
|
|
+ let mut guard = self.state.write()?;
|
|
|
+ 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, |storage, handles| storage.root = Some(handles))?;
|
|
|
Ok(creds)
|
|
|
}
|
|
@@ -1212,7 +1170,12 @@ impl CredStore for TpmCredStore {
|
|
|
policy_session,
|
|
|
&aead_key,
|
|
|
)?;
|
|
|
- Ok(ExportedCreds { sign, enc, params })
|
|
|
+ Ok(ExportedCreds {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ params,
|
|
|
+ writecap: root_creds.writecap.clone(),
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn import_root_creds(&self, password: &str, exported: ExportedCreds) -> Result<TpmCreds> {
|
|
@@ -1231,28 +1194,16 @@ impl CredStore for TpmCredStore {
|
|
|
guard
|
|
|
.context
|
|
|
.import_key(exported.enc, storage_key.private, auth, &aead_key)?;
|
|
|
- let cred_data = CredData { sign, enc };
|
|
|
+ let cred_data = CredData {
|
|
|
+ sign,
|
|
|
+ enc,
|
|
|
+ writecap: exported.writecap,
|
|
|
+ };
|
|
|
TpmCreds::new(cred_data, &self.state)
|
|
|
};
|
|
|
self.persist(&creds, |storage, handles| storage.root = Some(handles))?;
|
|
|
Ok(creds)
|
|
|
}
|
|
|
-
|
|
|
- fn request_writecap(&self, req_for: Principal) -> Result<Self::WritecapReq> {
|
|
|
- let node_creds = self.node_creds()?;
|
|
|
- let pub_key = node_creds.public_sign().clone();
|
|
|
- let body = WritecapReqBody { pub_key, req_for };
|
|
|
- unimplemented!()
|
|
|
- }
|
|
|
-
|
|
|
- fn issue_writecap(
|
|
|
- &self,
|
|
|
- request: &Self::WritecapReq,
|
|
|
- path: &crate::Path,
|
|
|
- password: &str,
|
|
|
- ) -> Result<Writecap> {
|
|
|
- unimplemented!()
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
impl<S: Scheme> AsymKeyPub<S> {
|
|
@@ -1380,6 +1331,7 @@ pub(crate) struct TpmCreds {
|
|
|
state: Arc<RwLock<State>>,
|
|
|
sign: KeyPair<Sign>,
|
|
|
enc: KeyPair<Encrypt>,
|
|
|
+ writecap: Option<Writecap>,
|
|
|
}
|
|
|
|
|
|
impl TpmCreds {
|
|
@@ -1388,13 +1340,20 @@ impl TpmCreds {
|
|
|
sign: key_handles.sign,
|
|
|
enc: key_handles.enc,
|
|
|
state: state.clone(),
|
|
|
+ writecap: key_handles.writecap,
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn init_root_writecap(&mut self, expires: Epoch) -> Result<()> {
|
|
|
+ let writecap = self.issue_writecap(self.principal(), Vec::new(), expires)?;
|
|
|
+ self.writecap = Some(writecap);
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-impl Owned for TpmCreds {
|
|
|
- fn owner_of_kind(&self, kind: HashKind) -> Principal {
|
|
|
- self.sign.public.owner_of_kind(kind)
|
|
|
+impl Principaled for TpmCreds {
|
|
|
+ fn principal_of_kind(&self, kind: HashKind) -> Principal {
|
|
|
+ self.sign.public.principal_of_kind(kind)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1471,7 +1430,15 @@ impl Decrypter for TpmCreds {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl CredsPriv for TpmCreds {}
|
|
|
+impl CredsPriv for TpmCreds {
|
|
|
+ fn writecap(&self) -> Option<&Writecap> {
|
|
|
+ self.writecap.as_ref()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn set_writecap(&mut self, writecap: Writecap) {
|
|
|
+ self.writecap = Some(writecap)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
impl Creds for TpmCreds {}
|
|
|
|
|
@@ -1749,30 +1716,86 @@ mod test {
|
|
|
let (harness, store) = test_store()?;
|
|
|
let expected = {
|
|
|
let creds = store.node_creds()?;
|
|
|
- creds.owner()
|
|
|
+ creds.principal()
|
|
|
};
|
|
|
drop(store);
|
|
|
let store = TpmCredStore::new(harness.context()?, harness.state_path())?;
|
|
|
let creds = store.node_creds()?;
|
|
|
- let actual = creds.owner();
|
|
|
+ let actual = creds.principal();
|
|
|
assert_eq!(expected, actual);
|
|
|
sign_verify_test(&creds)?;
|
|
|
encrypt_decrypt_test(&creds)?;
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ fn root_key_can_be_used_after_generation() {
|
|
|
+ let (_harness, store) = test_store().expect("failed to make test store");
|
|
|
+ let creds = store
|
|
|
+ .gen_root_creds(&"TimeInvariant")
|
|
|
+ .expect("failed to gen root creds");
|
|
|
+ let data = [1u8; 32];
|
|
|
+ creds
|
|
|
+ .sign(std::iter::once(data.as_slice()))
|
|
|
+ .expect("sign failed");
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn root_and_node_keys_generated() {
|
|
|
+ let (_harness, store) = test_store().expect("failed to make test store");
|
|
|
+ let _root_creds = store
|
|
|
+ .gen_root_creds(&"TranslationInvariant")
|
|
|
+ .expect("failed to gen root creds");
|
|
|
+ let _node_creds = store.node_creds().expect("failed to gen node creds");
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn verify_root_writecap() {
|
|
|
+ let (_harness, store) = test_store().expect("failed to make test store");
|
|
|
+ let root_creds = store
|
|
|
+ .gen_root_creds(&"TranslationInvariant")
|
|
|
+ .expect("failed to gen root creds");
|
|
|
+ let writecap = root_creds.writecap().expect("no root writecap was present");
|
|
|
+ let path = crate::Path {
|
|
|
+ root: root_creds.principal(),
|
|
|
+ components: Vec::new(),
|
|
|
+ };
|
|
|
+ verify_writecap(&writecap, &path).expect("failed to verify root writecap");
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn issue_writecap_to_node() {
|
|
|
+ let (_harness, store) = test_store().expect("failed to make test store");
|
|
|
+ let root_creds = store
|
|
|
+ .gen_root_creds(&"TranslationInvariant")
|
|
|
+ .expect("failed to gen root creds");
|
|
|
+ let path = crate::Path {
|
|
|
+ root: root_creds.principal(),
|
|
|
+ components: vec!["apps".to_string(), "comms".to_string()],
|
|
|
+ };
|
|
|
+ let node_creds = store.node_creds().expect("failed to gen node creds");
|
|
|
+ let writecap = root_creds
|
|
|
+ .issue_writecap(
|
|
|
+ node_creds.principal(),
|
|
|
+ path.components.clone(),
|
|
|
+ Epoch::now() + Duration::from_secs(3600),
|
|
|
+ )
|
|
|
+ .expect("failed to issue writecap");
|
|
|
+ verify_writecap(&writecap, &path).expect("failed to verify writecap");
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn root_key_persisted() -> Result<()> {
|
|
|
const PASSWORD: &str = "Scaramouch";
|
|
|
let (harness, store) = test_store()?;
|
|
|
let expected = {
|
|
|
let creds = store.gen_root_creds(PASSWORD)?;
|
|
|
- creds.owner()
|
|
|
+ creds.principal()
|
|
|
};
|
|
|
drop(store);
|
|
|
let store = TpmCredStore::new(harness.context()?, harness.state_path())?;
|
|
|
let creds = store.root_creds(PASSWORD)?;
|
|
|
- let actual = creds.owner();
|
|
|
+ let actual = creds.principal();
|
|
|
assert_eq!(expected, actual);
|
|
|
sign_verify_test(&creds)?;
|
|
|
encrypt_decrypt_test(&creds)?;
|
|
@@ -1780,7 +1803,7 @@ mod test {
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
- fn root_key_not_returned_when_password_wrong() -> Result<()> {
|
|
|
+ fn root_key_unusable_when_password_wrong() -> Result<()> {
|
|
|
let (harness, store) = test_store()?;
|
|
|
store.gen_root_creds("Galileo")?;
|
|
|
drop(store);
|