readpassphrase_3/
lib.rs

1// Copyright 2025
2//	Steven Dee
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions
6// are met:
7//
8// Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//
11// THIS SOFTWARE IS PROVIDED BY STEVEN DEE “AS IS” AND ANY EXPRESS
12// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14// ARE DISCLAIMED. IN NO EVENT SHALL STEVEN DEE BE LIABLE FOR ANY
15// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
17// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
18// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
19// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
20// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
21// IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23//! This library exposes a thin but usable wrapper around the C [`readpassphrase(3)`][0] function.
24//!
25//! # Usage
26//! For the simplest of cases, where you would just like to read a password from the console into a
27//! [`String`] to use elsewhere, you can use [`getpass`]:
28//! ```no_run
29//! # use readpassphrase_3::{getpass};
30//! let _ = getpass(c"Enter your password: ").expect("failed reading password");
31//! ```
32//!
33//! If you need to pass [`RppFlags`] or to control the buffer size, then you can use
34//! [`readpassphrase`] or [`readpassphrase_owned`] depending on your ownership requirements:
35//! ```no_run
36//! # use readpassphrase_3::{RppFlags, readpassphrase};
37//! let mut buf = vec![0u8; 256];
38//! let _ = readpassphrase(c"Password: ", &mut buf, RppFlags::default()).unwrap();
39//!
40//! # use readpassphrase_3::{readpassphrase_owned};
41//! let _ = readpassphrase_owned(c"Pass: ", buf, RppFlags::FORCELOWER).unwrap();
42//! ```
43//!
44//! # Security
45//! Sensitive data should be zeroed as soon as possible to avoid leaving it visible in the
46//! process’s address space. This crate ships with a minimal [`zeroize`] module that may be used
47//! for this purpose on the types taken and returned by these functions:
48//! ```no_run
49//! # use readpassphrase_3::{
50//! #     RppFlags,
51//! #     getpass,
52//! #     readpassphrase,
53//! #     readpassphrase_owned,
54//! #     zeroize::Zeroize,
55//! # };
56//! let mut pass = getpass(c"password: ").unwrap();
57//! // do_something_with(&pass);
58//! pass.zeroize();
59//!
60//! let mut buf = vec![0u8; 256];
61//! let res = readpassphrase(c"password: ", &mut buf, RppFlags::empty());
62//! // match_something_on(res);
63//! buf.zeroize();
64//!
65//! let mut pass = readpassphrase_owned(c"password: ", buf, RppFlags::empty()).unwrap();
66//! // do_something_with(&pass);
67//! pass.zeroize();
68//! ```
69//!
70//! ## `zeroize` feature
71//! This crate works well with the [`zeroize`][1] crate; for example, [`zeroize::Zeroizing`][2] may
72//! be used to zero buffer contents regardless of a function’s control flow:
73//! ```no_run
74//! # use readpassphrase_3::{Error, PASSWORD_LEN, RppFlags, getpass, readpassphrase};
75//! use zeroize::Zeroizing;
76//! # fn main() -> Result<(), Error> {
77//! let mut buf = Zeroizing::new(vec![0u8; PASSWORD_LEN]);
78//! let pass = readpassphrase(c"pass: ", &mut buf, RppFlags::REQUIRE_TTY)?;
79//! // do_something_that_can_fail_with(pass)?;
80//!
81//! // Or alternatively:
82//! let pass = Zeroizing::new(getpass(c"pass: ")?);
83//! // do_something_that_can_fail_with(&pass)?;
84//! # Ok(())
85//! # }
86//! ```
87//!
88//! If this crate’s `zeroize` feature is enabled, then its [`zeroize`] will be replaced by the
89//! upstream [`zeroize::Zeroize`][3].
90//!
91//! # “Mismatched types” errors
92//! The prompt strings in this API are references to [CStr], not [str]. This is because the
93//! underlying C function assumes that the prompt is a null-terminated string; were we to take
94//! `&str` instead of `&CStr`, we would need to make a copy of the prompt on every call.
95//!
96//! Most of the time, your prompts will be string literals; you can ask Rust to give you a `&CStr`
97//! literal by simply prepending `c` to the string:
98//! ```no_run
99//! # use readpassphrase_3::{Error, getpass};
100//! # fn main() -> Result<(), Error> {
101//! let _ = getpass(c"pass: ")?;
102//! //              ^
103//! //              |
104//! //              like this
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! # Windows Limitations
110//! The Windows implementation of `readpassphrase(3)` that we are using does not yet support UTF-8
111//! in prompts; they must be ASCII. It also does not yet support flags, and always behaves as
112//! though called with [`RppFlags::empty()`].
113//!
114//! [0]: https://man.openbsd.org/readpassphrase
115//! [1]: https://docs.rs/zeroize/latest/zeroize/
116//! [2]: https://docs.rs/zeroize/latest/zeroize/struct.Zeroizing.html
117//! [3]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
118
119use std::{ffi::CStr, fmt::Display, io, mem, str::Utf8Error};
120
121use bitflags::bitflags;
122use zeroize::Zeroize;
123
124/// Size of buffer used in [`getpass`].
125///
126/// Because `readpassphrase(3)` null-terminates its string, the actual maximum password length for
127/// [`getpass`] is 255.
128pub const PASSWORD_LEN: usize = 256;
129
130bitflags! {
131    /// Flags for controlling readpassphrase.
132    ///
133    /// The default flag `ECHO_OFF` is not represented here because `bitflags` [recommends against
134    /// zero-bit flags][0]; it may be specified as either [`RppFlags::empty()`] or
135    /// [`RppFlags::default()`].
136    ///
137    /// Note that the Windows `readpassphrase(3)` implementation always acts like it has been
138    /// passed `ECHO_OFF`, i.e., the flags are ignored.
139    ///
140    /// [0]: https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags
141    #[derive(Default)]
142    pub struct RppFlags: i32 {
143        /// Leave echo on.
144        const ECHO_ON     = 0x01;
145        /// Fail if there is no tty.
146        const REQUIRE_TTY = 0x02;
147        /// Force input to lower case.
148        const FORCELOWER  = 0x04;
149        /// Force input to upper case.
150        const FORCEUPPER  = 0x08;
151        /// Strip the high bit from input.
152        const SEVENBIT    = 0x10;
153        /// Read from stdin, not `/dev/tty`.
154        const STDIN       = 0x20;
155    }
156}
157
158/// Errors that can occur in readpassphrase.
159#[derive(Debug)]
160pub enum Error {
161    /// `readpassphrase(3)` itself encountered an error.
162    Io(io::Error),
163    /// The entered password was not UTF-8.
164    Utf8(Utf8Error),
165}
166
167/// Reads a passphrase using `readpassphrase(3)`, returning a [`&str`](str).
168///
169/// This function reads a password of up to `buf.len() - 1` bytes into `buf`. If the entered
170/// password is longer, it is truncated to the maximum length. If `readpasspharse(3)` itself fails,
171/// or if the entered password is not valid UTF-8, then [`Error`] is returned.
172///
173/// # Security
174/// The passed buffer might contain sensitive data, even if this function returns an error.
175/// Therefore it should be zeroed as soon as possible. This can be achieved, for example, with
176/// [`zeroize::Zeroizing`][0]:
177/// ```no_run
178/// # use readpassphrase_3::{PASSWORD_LEN, Error, RppFlags, readpassphrase};
179/// use zeroize::Zeroizing;
180/// # fn main() -> Result<(), Error> {
181/// let mut buf = Zeroizing::new(vec![0u8; PASSWORD_LEN]);
182/// let pass = readpassphrase(c"Pass: ", &mut buf, RppFlags::default())?;
183/// # Ok(())
184/// # }
185/// ```
186///
187/// [0]: https://docs.rs/zeroize/latest/zeroize/struct.Zeroizing.html
188pub fn readpassphrase<'a>(
189    prompt: &CStr,
190    buf: &'a mut [u8],
191    flags: RppFlags,
192) -> Result<&'a str, Error> {
193    unsafe {
194        let res = ffi::readpassphrase(
195            prompt.as_ptr(),
196            buf.as_mut_ptr().cast(),
197            buf.len(),
198            flags.bits(),
199        );
200        if res.is_null() {
201            return Err(io::Error::last_os_error().into());
202        }
203    }
204    Ok(CStr::from_bytes_until_nul(buf).unwrap().to_str()?)
205}
206
207/// Reads a passphrase using `readpassphrase(3)`, returning a [`String`].
208///
209/// Internally, this function uses a buffer of [`PASSWORD_LEN`] bytes, allowing for passwords up to
210/// `PASSWORD_LEN - 1` characters (accounting for the C null terminator.) If the entered passphrase
211/// is longer, it will be truncated to the maximum length.
212///
213/// # Security
214/// The returned `String` is owned by the caller, and therefore it is the caller’s responsibility
215/// to clear it when you are done with it:
216/// ```no_run
217/// # use readpassphrase_3::{Error, getpass, zeroize::Zeroize};
218/// # fn main() -> Result<(), Error> {
219/// let mut pass = getpass(c"Pass: ")?;
220/// _ = pass;
221/// pass.zeroize();
222/// # Ok(())
223/// # }
224/// ```
225pub fn getpass(prompt: &CStr) -> Result<String, Error> {
226    Ok(readpassphrase_owned(
227        prompt,
228        vec![0u8; PASSWORD_LEN],
229        RppFlags::empty(),
230    )?)
231}
232
233/// An error from [`readpassphrase_owned`].
234///
235/// This wraps [`Error`] but also contains the passed buffer, accessible via [`OwnedError::take`].
236/// If [`take`](OwnedError::take) is not called, the buffer is automatically zeroed on drop.
237#[derive(Debug)]
238pub struct OwnedError(Error, Option<Vec<u8>>);
239
240/// Reads a passphrase using `readpassphrase(3)`, returning a [`String`] reusing `buf`’s memory.
241///
242/// This function reads a passphrase of up to `buf.capacity() - 1` bytes. If the entered passphrase
243/// is longer, it will be truncated.
244///
245/// The returned [`String`] reuses `buf`’s memory; no copies are made. On error, the original
246/// buffer is instead returned via [`OwnedError`] and may be reused. `OwnedError` converts to
247/// [`Error`], so the `?` operator may be used with functions that return `Error`.
248///
249/// **NB**. Sometimes in Rust the capacity of a vector may be larger than you expect; if you need a
250/// precise limit on the length of the entered password, either use [`readpassphrase`] or truncate
251/// the returned string.
252///
253/// # Security
254/// The returned `String` is owned by the caller, and it is the caller’s responsibility to clear
255/// it. This can be done via [`zeroize`], e.g.:
256/// ```no_run
257/// # use readpassphrase_3::{
258/// #     PASSWORD_LEN,
259/// #     Error,
260/// #     RppFlags,
261/// #     readpassphrase_owned,
262/// #     zeroize::Zeroize,
263/// # };
264/// # fn main() -> Result<(), Error> {
265/// let buf = vec![0u8; PASSWORD_LEN];
266/// let mut pass = readpassphrase_owned(c"Pass: ", buf, RppFlags::default())?;
267/// _ = pass;
268/// pass.zeroize();
269/// # Ok(())
270/// # }
271/// ```
272pub fn readpassphrase_owned(
273    prompt: &CStr,
274    mut buf: Vec<u8>,
275    flags: RppFlags,
276) -> Result<String, OwnedError> {
277    readpassphrase_mut(prompt, &mut buf, flags).map_err(|e| {
278        buf.clear();
279        OwnedError(e, Some(buf))
280    })
281}
282
283// Reads a passphrase into `buf`’s maybe-uninitialized capacity and returns it as a `String`
284// reusing `buf`’s memory on success. This function serves to make it possible to write
285// `readpassphrase_owned` without either pre-initializing the buffer or invoking undefined
286// behavior by constructing a maybe-uninitialized slice.
287fn readpassphrase_mut(prompt: &CStr, buf: &mut Vec<u8>, flags: RppFlags) -> Result<String, Error> {
288    unsafe {
289        let res = ffi::readpassphrase(
290            prompt.as_ptr(),
291            buf.as_mut_ptr().cast(),
292            buf.capacity(),
293            flags.bits(),
294        );
295        if res.is_null() {
296            return Err(io::Error::last_os_error().into());
297        }
298        let res = CStr::from_ptr(res).to_str()?;
299        buf.set_len(res.len());
300        Ok(String::from_utf8_unchecked(mem::take(buf)))
301    }
302}
303
304/// Securely zero the memory in `buf`.
305///
306/// This function zeroes the full capacity of `buf`, erasing any sensitive data in it. It is
307/// a simple shim for [`zeroize`] and the latter should be used instead.
308///
309/// # Usage
310/// The following are equivalent:
311/// ```no_run
312/// # use readpassphrase_3::{explicit_bzero, zeroize::Zeroize};
313/// let mut buf = vec![1u8; 1];
314/// // 1.
315/// explicit_bzero(&mut buf);
316/// // 2.
317/// buf.zeroize();
318/// ```
319#[deprecated(since = "0.6.0", note = "use zeroize::Zeroize instead")]
320pub fn explicit_bzero(buf: &mut Vec<u8>) {
321    buf.zeroize();
322}
323
324impl OwnedError {
325    /// Take `buf` out of the error.
326    ///
327    /// Returns empty [`Vec`] after the first call.
328    pub fn take(&mut self) -> Vec<u8> {
329        self.1.take().unwrap_or_default()
330    }
331}
332
333impl Drop for OwnedError {
334    fn drop(&mut self) {
335        self.1.take().as_mut().map(zeroize::Zeroize::zeroize);
336    }
337}
338
339impl From<OwnedError> for Error {
340    fn from(mut value: OwnedError) -> Self {
341        mem::replace(&mut value.0, Error::Io(io::ErrorKind::Other.into()))
342    }
343}
344
345impl From<io::Error> for Error {
346    fn from(value: io::Error) -> Self {
347        Error::Io(value)
348    }
349}
350
351impl From<Utf8Error> for Error {
352    fn from(value: Utf8Error) -> Self {
353        Error::Utf8(value)
354    }
355}
356
357impl core::error::Error for OwnedError {
358    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
359        Some(&self.0)
360    }
361}
362
363impl Display for OwnedError {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        self.0.fmt(f)
366    }
367}
368
369impl core::error::Error for Error {
370    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
371        match self {
372            Error::Io(e) => Some(e),
373            Error::Utf8(e) => Some(e),
374        }
375    }
376}
377
378impl Display for Error {
379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        match self {
381            Error::Io(e) => e.fmt(f),
382            Error::Utf8(e) => e.fmt(f),
383        }
384    }
385}
386
387/// A minimal in-crate implementation of [`zeroize::Zeroize`][0].
388///
389/// This provides compile-fenced memory zeroing for [`String`]s and [`Vec`]s without needing to
390/// depend on the `zeroize` crate.
391///
392/// If the optional `zeroize` feature is enabled, then the trait is replaced with a re-export of
393/// [`zeroize::Zeroize`] itself.
394///
395/// [0]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
396#[cfg(any(docsrs, not(feature = "zeroize")))]
397pub mod zeroize {
398    use std::{arch::asm, mem::MaybeUninit};
399
400    /// Trait for securely erasing values from memory.
401    pub trait Zeroize {
402        fn zeroize(&mut self);
403    }
404
405    impl Zeroize for Vec<u8> {
406        fn zeroize(&mut self) {
407            self.clear();
408            let buf = self.spare_capacity_mut();
409            buf.fill(MaybeUninit::zeroed());
410            compile_fence(buf);
411        }
412    }
413
414    impl Zeroize for String {
415        fn zeroize(&mut self) {
416            unsafe { self.as_mut_vec() }.zeroize();
417        }
418    }
419
420    impl Zeroize for [u8] {
421        fn zeroize(&mut self) {
422            self.fill(0);
423            compile_fence(self);
424        }
425    }
426
427    fn compile_fence<T>(buf: &[T]) {
428        unsafe {
429            asm!(
430                "/* {ptr} */",
431                ptr = in(reg) buf.as_ptr(),
432                options(nostack, preserves_flags, readonly)
433            );
434        }
435    }
436}
437
438#[cfg(all(not(docsrs), feature = "zeroize"))]
439pub mod zeroize {
440    pub use zeroize::Zeroize;
441}
442
443mod ffi {
444    use std::ffi::{c_char, c_int};
445
446    unsafe extern "C" {
447        pub(crate) unsafe fn readpassphrase(
448            prompt: *const c_char,
449            buf: *mut c_char,
450            bufsiz: usize,
451            flags: c_int,
452        ) -> *mut c_char;
453    }
454}