Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:51:11.832Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

encrypted_password.rs

103 lines · 4.1 KB · rust
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}
103
Served at tenant.openagents/omega Member data and write actions are omitted.