use std::{
fs,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
use qos_p256::P256Pair;
use crate::protocol::{
ProtocolError, services::boot::VersionedManifestEnvelope,
};
#[derive(Debug, Clone)]
pub struct QuorumKeyHandle {
quorum: String,
}
impl QuorumKeyHandle {
#[must_use]
pub fn new(quorum: String) -> Self {
Self { quorum }
}
pub fn get_quorum_key(&self) -> Result<P256Pair, ProtocolError> {
let pair = P256Pair::from_hex_file(&self.quorum)
.map_err(ProtocolError::FailedToGetQuorumKey)?;
Ok(pair)
}
}
#[derive(Debug, Clone, Copy)]
pub struct EphemeralKeyHandle<S = String> {
ephemeral_key_path: S,
}
impl<P> EphemeralKeyHandle<P>
where
P: AsRef<Path>,
{
#[must_use]
pub fn new(ephemeral_key_path: P) -> Self {
Self { ephemeral_key_path }
}
pub fn get_ephemeral_key(&self) -> Result<P256Pair, ProtocolError> {
let pair = P256Pair::from_hex_file(&self.ephemeral_key_path)
.map_err(ProtocolError::FailedToGetEphemeralKey)?;
Ok(pair)
}
}
#[derive(Debug, Clone)]
pub struct Handles {
ephemeral: EphemeralKeyHandle,
quorum: QuorumKeyHandle,
manifest: String,
pivot: String,
}
impl Handles {
#[must_use]
pub fn new(
ephemeral: String,
quorum: String,
manifest: String,
pivot: String,
) -> Self {
Self {
ephemeral: EphemeralKeyHandle::new(ephemeral),
quorum: QuorumKeyHandle::new(quorum),
manifest,
pivot,
}
}
pub fn get_ephemeral_key(&self) -> Result<P256Pair, ProtocolError> {
self.ephemeral.get_ephemeral_key()
}
pub fn put_ephemeral_key(
&self,
pair: &P256Pair,
) -> Result<(), ProtocolError> {
Self::write_as_read_only(
&self.ephemeral.ephemeral_key_path,
&pair.to_master_seed_hex(),
ProtocolError::FailedToPutEphemeralKey,
)
}
pub fn rotate_ephemeral_key(
&self,
new_pair: &P256Pair,
) -> Result<(), ProtocolError> {
let path = Path::new(&self.ephemeral.ephemeral_key_path);
if !path.exists() {
Err(ProtocolError::CannotRotateNonExistentEphemeralKey)?;
}
fs::remove_file(path).map_err(|e| {
ProtocolError::CannotDeleteEphemeralKey(e.to_string())
})?;
Self::write_as_read_only(
path,
&new_pair.to_master_seed_hex(),
ProtocolError::FailedToPutEphemeralKey,
)
}
pub fn get_quorum_key(&self) -> Result<P256Pair, ProtocolError> {
self.quorum.get_quorum_key()
}
pub fn put_quorum_key(&self, pair: &P256Pair) -> Result<(), ProtocolError> {
Self::write_as_read_only(
&self.quorum.quorum,
&pair.to_master_seed_hex(),
ProtocolError::FailedToPutQuorumKey,
)
}
#[must_use]
pub fn quorum_key_exists(&self) -> bool {
Path::new(&self.quorum.quorum).exists()
}
pub fn get_manifest_envelope(
&self,
) -> Result<VersionedManifestEnvelope, ProtocolError> {
let contents = fs::read(&self.manifest)
.map_err(|_| ProtocolError::FailedToGetManifestEnvelope)?;
let manifest =
VersionedManifestEnvelope::try_from_slice_compat(&contents)
.map_err(|_| ProtocolError::FailedToGetManifestEnvelope)?;
Ok(manifest)
}
pub fn put_manifest_envelope<E>(
&self,
manifest_envelope: E,
) -> Result<(), ProtocolError>
where
E: Into<VersionedManifestEnvelope>,
{
let manifest_envelope = manifest_envelope.into();
Self::write_as_read_only(
&self.manifest,
&manifest_envelope
.to_storage_vec()
.map_err(|_| ProtocolError::FailedToPutManifestEnvelope)?,
ProtocolError::FailedToPutManifestEnvelope,
)
}
pub(crate) fn mutate_manifest_envelope<
F: FnOnce(VersionedManifestEnvelope) -> VersionedManifestEnvelope,
>(
&self,
mutate: F,
) -> Result<(), ProtocolError> {
let manifest_envelope = self.get_manifest_envelope()?;
let manifest_envelope = mutate(manifest_envelope);
fs::set_permissions(
&self.manifest,
std::fs::Permissions::from_mode(0o666),
)?;
fs::write(
&self.manifest,
manifest_envelope
.to_storage_vec()
.map_err(|_| ProtocolError::FailedToPutManifestEnvelope)?,
)
.map_err(|_| ProtocolError::FailedToPutManifestEnvelope)?;
fs::set_permissions(
&self.manifest,
std::fs::Permissions::from_mode(0o444),
)?;
Ok(())
}
#[must_use]
pub fn manifest_envelope_exists(&self) -> bool {
Path::new(&self.manifest).exists()
}
#[must_use]
pub fn pivot_path(&self) -> String {
self.pivot.clone()
}
pub fn put_pivot(&self, pivot: &[u8]) -> Result<(), ProtocolError> {
if Path::new(&self.pivot).exists() {
Err(ProtocolError::CannotModifyPostPivotStatic)?;
}
if let Some(parent) = Path::new(&self.pivot).parent()
&& !parent.exists()
{
fs::create_dir_all(parent)
.map_err(|_| ProtocolError::FailedToPutPivot)?;
}
fs::write(&self.pivot, pivot)
.map_err(|_| ProtocolError::FailedToPutPivot)?;
fs::set_permissions(
&self.pivot,
std::fs::Permissions::from_mode(0o111),
)
.map_err(|_| ProtocolError::FailedToPutPivot)?;
Ok(())
}
#[must_use]
pub fn pivot_exists(&self) -> bool {
Path::new(&self.pivot).exists()
}
fn write_as_read_only<P: AsRef<Path>>(
path: P,
buf: &[u8],
err: ProtocolError,
) -> Result<(), ProtocolError> {
if path.as_ref().exists() {
Err(ProtocolError::CannotModifyPostPivotStatic)?;
}
if let Some(parent) = path.as_ref().parent()
&& !parent.exists()
{
fs::create_dir_all(parent).map_err(|_| err.clone())?;
}
let tmp_path = PathBuf::from(path.as_ref()).with_extension("tmp");
fs::write(&tmp_path, buf).map_err(|_| err.clone())?;
fs::rename(&tmp_path, &path)?;
fs::set_permissions(&path, fs::Permissions::from_mode(0o444))
.map_err(|_| err)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use qos_crypto::sha_256;
use qos_test_primitives::PathWrapper;
use super::*;
use crate::protocol::services::boot::{
Manifest, ManifestEnvelope, ManifestSet, Namespace, NitroConfig,
PatchSet, PivotConfig, RestartPolicy, ShareSet,
};
#[test]
fn put_ephemeral_key_is_read_only_write() {
let pivot_file =
PathWrapper::from("put_ephemeral_key_is_read_only_write.pivot");
let ephemeral_file = PathWrapper::from(
"put_ephemeral_key_is_read_only_write_eph.secret",
);
let quorum_file = PathWrapper::from(
"put_ephemeral_key_is_read_only_write_quor.secret",
);
let manifest_file =
PathWrapper::from("put_ephemeral_key_is_read_only_write.manifest");
let handles = Handles::new(
ephemeral_file.display().to_string(),
quorum_file.display().to_string(),
manifest_file.display().to_string(),
pivot_file.display().to_string(),
);
let ephemeral_key = P256Pair::generate().unwrap();
let result = handles.put_ephemeral_key(&ephemeral_key);
let error = handles.put_ephemeral_key(&ephemeral_key).unwrap_err();
assert!(result.is_ok());
assert_eq!(error, ProtocolError::CannotModifyPostPivotStatic);
assert!(handles.get_ephemeral_key().unwrap() == ephemeral_key);
}
#[test]
fn put_quorum_key_is_read_only_write() {
let pivot_file =
PathWrapper::from("put_quorum_key_is_read_only_write.pivot");
let ephemeral_file =
PathWrapper::from("put_quorum_key_is_read_only_write_eph.secret");
let quorum_file =
PathWrapper::from("put_quorum_key_is_read_only_write_quor.secret");
let manifest_file =
PathWrapper::from("put_quorum_key_is_read_only_write.manifest");
let handles = Handles::new(
ephemeral_file.display().to_string(),
quorum_file.display().to_string(),
manifest_file.display().to_string(),
pivot_file.display().to_string(),
);
let quorum_key = P256Pair::generate().unwrap();
let result = handles.put_quorum_key(&quorum_key);
let error = handles.put_quorum_key(&quorum_key).unwrap_err();
assert!(result.is_ok());
assert_eq!(error, ProtocolError::CannotModifyPostPivotStatic);
assert!(handles.quorum_key_exists());
assert!(handles.get_quorum_key().unwrap() == quorum_key);
}
#[test]
fn put_pivot_is_read_only_write() {
let pivot_file =
PathWrapper::from("put_pivot_is_read_only_write.pivot");
let ephemeral_file =
PathWrapper::from("put_pivot_is_read_only_write_eph.secret");
let quorum_file =
PathWrapper::from("put_pivot_is_read_only_write_quor.secret");
let manifest_file =
PathWrapper::from("put_pivot_is_read_only_write.manifest");
let handles = Handles::new(
ephemeral_file.display().to_string(),
quorum_file.display().to_string(),
manifest_file.display().to_string(),
pivot_file.display().to_string(),
);
let pivot = b"this is a pivot binary".to_vec();
let result = handles.put_pivot(&pivot);
let error = handles.put_pivot(&pivot).unwrap_err();
assert!(result.is_ok());
assert_eq!(error, ProtocolError::CannotModifyPostPivotStatic);
assert!(handles.pivot_exists());
}
#[test]
fn put_manifest_is_read_only_write() {
let pivot_file =
PathWrapper::from("put_manifest_is_read_only_write.pivot");
let ephemeral_file =
PathWrapper::from("put_manifest_is_read_only_write_eph.secret");
let quorum_file =
PathWrapper::from("put_manifest_is_read_only_write_quor.secret");
let manifest_file =
PathWrapper::from("put_manifest_is_read_only_write.manifest");
let handles = Handles::new(
ephemeral_file.display().to_string(),
quorum_file.display().to_string(),
manifest_file.display().to_string(),
pivot_file.display().to_string(),
);
let pivot = b"this is a pivot binary".to_vec();
let manifest = Manifest {
namespace: Namespace {
nonce: 420,
name: "vape lord".to_string(),
quorum_key: P256Pair::generate()
.unwrap()
.public_key()
.to_bytes(),
},
enclave: NitroConfig {
pcr0: vec![4; 32],
pcr1: vec![3; 32],
pcr2: vec![2; 32],
pcr3: vec![1; 32],
aws_root_certificate: b"cert lord".to_vec(),
qos_commit: "mock qos commit".to_string(),
},
pivot: PivotConfig {
hash: sha_256(&pivot),
restart: RestartPolicy::Always,
args: vec![],
..Default::default()
},
manifest_set: ManifestSet { threshold: 2, members: vec![] },
share_set: ShareSet { threshold: 2, members: vec![] },
patch_set: PatchSet::default(),
};
let manifest_envelope =
VersionedManifestEnvelope::V1(ManifestEnvelope {
manifest,
manifest_set_approvals: vec![],
share_set_approvals: vec![],
});
let result = handles.put_manifest_envelope(&manifest_envelope);
let error =
handles.put_manifest_envelope(&manifest_envelope).unwrap_err();
assert!(result.is_ok());
assert_eq!(error, ProtocolError::CannotModifyPostPivotStatic);
assert!(handles.manifest_envelope_exists());
assert_eq!(handles.get_manifest_envelope().unwrap(), manifest_envelope);
}
}