encrypted_password.rs

  1//! This module provides [EncryptedPassword] for storage of passwords in memory.
  2//! On Windows that's implemented with CryptProtectMemory/CryptUnprotectMemory; on other platforms it just falls through
  3//! to string for now.
  4//!
  5//! The "safety" of this module lies in exploiting visibility rules of Rust:
  6//! 1. No outside module has access to the internal representation of [EncryptedPassword].
  7//! 2. [EncryptedPassword] cannot be converted into a [String] or any other plaintext representation.
  8//! All use cases that do need such functionality (of which we have two right now) are implemented within this module.
  9//!
 10//! Note that this is not bulletproof.
 11//! 1. [ProcessExt] is implemented for [smol::process::Command], which is a builder for smol processes.
 12//! Before the process itself is spawned the contents of [EncryptedPassword] are unencrypted in env var storage of said builder.
 13//! 2. We're also sending plaintext passwords over RPC with [proto::AskPassResponse]. Go figure how great that is.
 14//!
 15//! Still, the goal of this module is to not have passwords laying around nilly-willy in memory.
 16//! We do not claim that it is fool-proof.
 17use anyhow::Result;
 18use zeroize::Zeroize;
 19
 20type LengthWithoutPadding = u32;
 21#[derive(Clone)]
 22pub struct EncryptedPassword(Vec<u8>, LengthWithoutPadding);
 23
 24impl Drop for EncryptedPassword {
 25    fn drop(&mut self) {
 26        self.0.zeroize();
 27        self.1.zeroize();
 28    }
 29}
 30
 31impl TryFrom<&str> for EncryptedPassword {
 32    type Error = anyhow::Error;
 33    fn try_from(password: &str) -> Result<EncryptedPassword> {
 34        let len: u32 = password.len().try_into()?;
 35        #[cfg(windows)]
 36        {
 37            use windows::Win32::Security::Cryptography::{
 38                CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptProtectMemory,
 39            };
 40            let mut value = password.bytes().collect::<Vec<_>>();
 41            let padded_length = len.next_multiple_of(CRYPTPROTECTMEMORY_BLOCK_SIZE);
 42            if padded_length != len {
 43                value.resize(padded_length as usize, 0);
 44            }
 45            if len != 0 {
 46                unsafe {
 47                    CryptProtectMemory(
 48                        value.as_mut_ptr() as _,
 49                        padded_length,
 50                        CRYPTPROTECTMEMORY_SAME_PROCESS,
 51                    )?;
 52                }
 53            }
 54            Ok(Self(value, len))
 55        }
 56        #[cfg(not(windows))]
 57        Ok(Self(String::from(password).into(), len))
 58    }
 59}
 60
 61/// Read the docs for [EncryptedPassword]; please take care of not storing the plaintext string in memory for extended
 62/// periods of time.
 63pub struct IKnowWhatIAmDoingAndIHaveReadTheDocs;
 64
 65impl EncryptedPassword {
 66    pub fn decrypt(mut self, _: IKnowWhatIAmDoingAndIHaveReadTheDocs) -> Result<String> {
 67        #[cfg(windows)]
 68        {
 69            use anyhow::Context;
 70            use windows::Win32::Security::Cryptography::{
 71                CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS,
 72                CryptUnprotectMemory,
 73            };
 74            assert_eq!(
 75                self.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
 76                0,
 77                "Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
 78                self.0.len(),
 79                CRYPTPROTECTMEMORY_BLOCK_SIZE
 80            );
 81            if self.1 != 0 {
 82                unsafe {
 83                    CryptUnprotectMemory(
 84                        self.0.as_mut_ptr() as _,
 85                        self.0.len().try_into()?,
 86                        CRYPTPROTECTMEMORY_SAME_PROCESS,
 87                    )
 88                    .context("while decrypting a SSH password")?
 89                };
 90
 91                {
 92                    // Remove padding
 93                    _ = self.0.drain(self.1 as usize..);
 94                }
 95            }
 96
 97            Ok(String::from_utf8(std::mem::take(&mut self.0))?)
 98        }
 99        #[cfg(not(windows))]
100        Ok(String::from_utf8(std::mem::take(&mut self.0))?)
101    }
102}