Skip to main content

zebra_rpc/server/
cookie.rs

1//! Cookie-based authentication for the RPC server.
2
3use base64::{engine::general_purpose::STANDARD, Engine as _};
4use color_eyre::Result;
5use rand::RngCore;
6use subtle::ConstantTimeEq;
7
8use std::{
9    fs::{remove_file, File},
10    io::Write,
11    path::Path,
12};
13
14#[cfg(unix)]
15use std::os::unix::fs::OpenOptionsExt;
16
17/// The name of the cookie file on the disk
18const FILE: &str = ".cookie";
19
20/// If the RPC authentication is enabled, all requests must contain this cookie.
21#[derive(Clone, Debug)]
22pub struct Cookie(String);
23
24impl Cookie {
25    /// Constant-time comparison to prevent timing side-channels.
26    pub fn authenticate(&self, passwd: String) -> bool {
27        if passwd.len() != self.0.len() {
28            return false;
29        }
30        passwd.as_bytes().ct_eq(self.0.as_bytes()).into()
31    }
32}
33
34impl Default for Cookie {
35    fn default() -> Self {
36        let mut bytes = [0u8; 32];
37        rand::thread_rng().fill_bytes(&mut bytes);
38
39        Self(STANDARD.encode(bytes))
40    }
41}
42
43/// Writes the given cookie to the given dir.
44///
45/// Uses restrictive file permissions (0600 on Unix) to prevent other
46/// local users from reading the cookie secret.
47pub fn write_to_disk(cookie: &Cookie, dir: &Path) -> Result<()> {
48    std::fs::create_dir_all(dir)?;
49
50    let cookie_path = dir.join(FILE);
51
52    if cookie_path
53        .symlink_metadata()
54        .map(|m| m.file_type().is_symlink())
55        .unwrap_or(false)
56    {
57        return Err(color_eyre::eyre::eyre!(
58            "cookie path {cookie_path:?} is a symlink, refusing to write"
59        ));
60    }
61
62    let mut file = create_owner_only_file(&cookie_path)?;
63    file.write_all(format!("__cookie__:{}", cookie.0).as_bytes())?;
64
65    tracing::info!("RPC auth cookie written to disk");
66
67    Ok(())
68}
69
70/// Creates a file readable and writable only by the owner.
71///
72/// On Unix, this sets mode 0600 regardless of umask.
73/// On Windows, default ACLs already restrict access to the creating user,
74/// so no explicit hardening is needed.
75fn create_owner_only_file(path: &Path) -> Result<File> {
76    let mut opts = std::fs::OpenOptions::new();
77    opts.write(true).create(true).truncate(true);
78
79    #[cfg(unix)]
80    opts.mode(0o600);
81
82    Ok(opts.open(path)?)
83}
84
85/// Removes a cookie from the given dir.
86pub fn remove_from_disk(dir: &Path) -> Result<()> {
87    remove_file(dir.join(FILE))?;
88
89    tracing::info!("RPC auth cookie removed from disk");
90
91    Ok(())
92}