use std::{fs, io, path::Path, sync::Arc};
use fs_mistrust::Mistrust;
use safelog::Sensitive;
use zeroize::Zeroizing;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum RpcAuth {
None,
Cookie {
secret: Cookie,
server_address: String,
},
}
#[derive(Clone, Debug)]
pub struct Cookie {
value: Sensitive<Zeroizing<[u8; COOKIE_LEN]>>,
}
impl AsRef<[u8; COOKIE_LEN]> for Cookie {
fn as_ref(&self) -> &[u8; COOKIE_LEN] {
self.value.as_inner()
}
}
pub const COOKIE_LEN: usize = 32;
pub const COOKIE_PREFIX_LEN: usize = 32;
pub const COOKIE_PREFIX: &[u8; COOKIE_PREFIX_LEN] = b"====== arti-rpc-cookie-v1 ======";
impl Cookie {
#[cfg(feature = "rpc-client")]
pub fn load(path: &Path, mistrust: &Mistrust) -> Result<Cookie, CookieAccessError> {
use std::io::Read;
mistrust.verifier().check(path)?;
let mut file = fs::OpenOptions::new().read(true).open(path)?;
let mut buf = [0_u8; COOKIE_PREFIX_LEN];
file.read_exact(&mut buf)?;
if &buf != COOKIE_PREFIX {
return Err(CookieAccessError::FileFormat);
}
let mut cookie = Cookie {
value: Default::default(),
};
file.read_exact(cookie.value.as_mut().as_mut())?;
if file.read(&mut buf)? != 0 {
return Err(CookieAccessError::FileFormat);
}
Ok(cookie)
}
#[cfg(feature = "rpc-server")]
pub fn create<R: rand::CryptoRng + rand::RngCore>(
path: &Path,
rng: &mut R,
mistrust: &Mistrust,
) -> Result<Cookie, CookieAccessError> {
use std::io::Write;
let parent = path.parent().ok_or(CookieAccessError::UnusablePath)?;
let dir = mistrust.verifier().make_secure_dir(parent)?;
let mut file = dir.open(
path.file_name().ok_or(CookieAccessError::UnusablePath)?,
fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true),
)?;
file.write_all(&COOKIE_PREFIX[..])?;
let mut cookie = Cookie {
value: Default::default(),
};
rng.fill_bytes(cookie.value.as_mut().as_mut());
file.write_all(cookie.value.as_inner().as_ref())?;
Ok(cookie)
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum CookieAccessError {
#[error("Unable to access cookie file")]
Access(#[from] fs_mistrust::Error),
#[error("IO error while accessing cookie file")]
Io(#[source] Arc<io::Error>),
#[error("Could not find parent directory or filename for cookie file")]
UnusablePath,
#[error("Path did not point to a cookie file")]
FileFormat,
}
impl From<io::Error> for CookieAccessError {
fn from(err: io::Error) -> Self {
CookieAccessError::Io(Arc::new(err))
}
}
impl crate::HasClientErrorAction for CookieAccessError {
fn client_action(&self) -> crate::ClientErrorAction {
use crate::ClientErrorAction as A;
use CookieAccessError as E;
match self {
E::Access(err) => err.client_action(),
E::Io(err) => crate::fs_error_action(err.as_ref()),
E::UnusablePath => A::Decline,
E::FileFormat => A::Decline,
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
use crate::testing::tempdir;
#[test]
#[cfg(all(feature = "rpc-client", feature = "rpc-server"))]
fn cookie_file() {
let (_tempdir, dir, mistrust) = tempdir();
let path1 = dir.join("foo/foo.cookie");
let path2 = dir.join("bar.cookie");
let s_c1 = Cookie::create(path1.as_path(), &mut rand::thread_rng(), &mistrust).unwrap();
let s_c2 = Cookie::create(path2.as_path(), &mut rand::thread_rng(), &mistrust).unwrap();
assert_ne!(s_c1.as_ref(), s_c2.as_ref());
let c_c1 = Cookie::load(path1.as_path(), &mistrust).unwrap();
let c_c2 = Cookie::load(path2.as_path(), &mistrust).unwrap();
assert_eq!(s_c1.as_ref(), c_c1.as_ref());
assert_eq!(s_c2.as_ref(), c_c2.as_ref());
}
}