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}