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 endeavors to expose a thin wrapper around the C [`readpassphrase(3)`][0] function.
24//!
25//! Three different interfaces are exposed; for most purposes, you will want to use either
26//! [`getpass`] (for simple password entry) or [`readpassphrase`] (when you need flags from
27//! `readpassphrase(3)` or need more control over the memory.)
28//!
29//! A [`readpassphrase_owned`] function is also provided that takes ownership of the passed buffer
30//! and returns an owned [`String`] reusing that buffer’s memory.
31//!
32//! Sensitive data should be zeroed as soon as possible to avoid leaving it visible in the
33//! process’s address space.
34//!
35//! # Usage
36//! To read a passphrase from the console:
37//! ```no_run
38//! # use readpassphrase_3::{getpass, zeroize::Zeroize};
39//! let mut pass = getpass(c"password: ").unwrap();
40//! // do_something_with(&pass);
41//! pass.zeroize();
42//! ```
43//!
44//! To control the buffer size or (on non-Windows) flags:
45//! ```no_run
46//! # use readpassphrase_3::{RppFlags, readpassphrase};
47//! # let mut buf = vec![0u8; 1];
48//! let pass = readpassphrase(c"pass: ", &mut buf, RppFlags::ECHO_ON).unwrap();
49//! // do_something_with(pass);
50//! ```
51//!
52//! To do so while transferring ownership:
53//! ```no_run
54//! # use readpassphrase_3::{Error, RppFlags, readpassphrase_owned, zeroize::Zeroize};
55//! # fn main() -> Result<(), Error> {
56//! # let buf = vec![0u8; 1];
57//! let mut pass = readpassphrase_owned(c"pass: ", buf, RppFlags::empty())?;
58//! // do_something_with(&pass);
59//! pass.zeroize();
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! ## Zeroizing memory
65//! This crate works well with the [`zeroize`][1] crate; for example, [`zeroize::Zeroizing`][2] may
66//! be used to zero buffer contents regardless of a function’s control flow:
67//! ```no_run
68//! # use readpassphrase_3::{Error, PASSWORD_LEN, RppFlags, getpass, readpassphrase};
69//! use zeroize::Zeroizing;
70//! # fn main() -> Result<(), Error> {
71//! let mut buf = Zeroizing::new(vec![0u8; PASSWORD_LEN]);
72//! let pass = readpassphrase(c"pass: ", &mut buf, RppFlags::REQUIRE_TTY)?;
73//! // do_something_that_can_fail_with(pass)?;
74//!
75//! // Or alternatively:
76//! let pass = Zeroizing::new(getpass(c"pass: ")?);
77//! // do_something_that_can_fail_with(&pass)?;
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! This crate itself provides a minimal subset of [`zeroize`] that works on the types taken and
83//! returned by its other methods. If the `zeroize` feature is enabled, this minimal subset is
84//! replaced by [`zeroize::Zeroize`][3].
85//!
86//! # “Mismatched types” errors
87//! The prompt strings in this API are references to [CStr], not [str]. This is because the
88//! underlying C function assumes that the prompt is a null-terminated string; were we to take
89//! `&str` instead of `&CStr`, we would need to make a copy of the prompt on every call.
90//!
91//! Most of the time, your prompts will be string literals; you can ask Rust to give you a `&CStr`
92//! literal by simply prepending `c` to the string:
93//! ```no_run
94//! # use readpassphrase_3::{Error, getpass};
95//! # fn main() -> Result<(), Error> {
96//! let _ = getpass(c"pass: ")?;
97//! //              ^
98//! //              |
99//! //              like this
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! # Windows Limitations
105//! The Windows implementation of `readpassphrase(3)` that we are using does not yet support UTF-8
106//! in prompts; they must be ASCII. It also does not yet support flags, and always behaves as
107//! though called with [`RppFlags::empty()`].
108//!
109//! [0]: https://man.openbsd.org/readpassphrase
110//! [1]: https://docs.rs/zeroize/latest/zeroize/
111//! [2]: https://docs.rs/zeroize/latest/zeroize/struct.Zeroizing.html
112//! [3]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
113
114use std::{ffi::CStr, fmt::Display, io, mem, str::Utf8Error};
115
116use bitflags::bitflags;
117use zeroize::Zeroize;
118
119/// Size of buffer used in [`getpass`].
120///
121/// Because `readpassphrase(3)` null-terminates its string, the actual maximum password length for
122/// [`getpass`] is 255.
123pub const PASSWORD_LEN: usize = 256;
124
125bitflags! {
126    /// Flags for controlling readpassphrase.
127    ///
128    /// The default flag `ECHO_OFF` is not represented here because `bitflags` [recommends against
129    /// zero-bit flags][0]; it may be specified as either [`RppFlags::empty()`] or
130    /// [`RppFlags::default()`].
131    ///
132    /// Note that the Windows `readpassphrase(3)` implementation always acts like it has been
133    /// passed `ECHO_OFF`, i.e., the flags are ignored.
134    ///
135    /// [0]: https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags
136    #[derive(Default)]
137    pub struct RppFlags: i32 {
138        /// Leave echo on.
139        const ECHO_ON     = 0x01;
140        /// Fail if there is no tty.
141        const REQUIRE_TTY = 0x02;
142        /// Force input to lower case.
143        const FORCELOWER  = 0x04;
144        /// Force input to upper case.
145        const FORCEUPPER  = 0x08;
146        /// Strip the high bit from input.
147        const SEVENBIT    = 0x10;
148        /// Read from stdin, not `/dev/tty`.
149        const STDIN       = 0x20;
150    }
151}
152
153/// Errors that can occur in readpassphrase.
154#[derive(Debug)]
155pub enum Error {
156    /// `readpassphrase(3)` itself encountered an error.
157    Io(io::Error),
158    /// The entered password was not UTF-8.
159    Utf8(Utf8Error),
160}
161
162/// Reads a passphrase using `readpassphrase(3)`.
163///
164/// This function tries to faithfully wrap `readpassphrase(3)` without overhead; the only
165/// additional work it does is:
166/// 1. It converts from a Rust byte slice to a C pointer/length pair going in.
167/// 2. It converts from a C `char *` to a Rust UTF-8 `&str` coming out.
168/// 3. It translates errors from `errno` (or string conversion) into [`Result`].
169///
170/// This function reads a passphrase of up to `buf.len() - 1` bytes. If the entered passphrase is
171/// longer, it will be truncated.
172///
173/// # Security
174/// The passed buffer might contain sensitive data even if this function returns an error (for
175/// example, if the contents are not valid UTF-8.) Therefore it should be zeroed as soon as
176/// possible. This can be achieved, for example, with [`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 it as 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.
212///
213/// The passed flags are always [`RppFlags::default()`], i.e. `ECHO_OFF`.
214///
215/// # Security
216/// The returned `String` is owned by the caller, and therefore it is the caller’s responsibility
217/// to clear it when you are done with it:
218/// ```no_run
219/// # use readpassphrase_3::{Error, getpass, zeroize::Zeroize};
220/// # fn main() -> Result<(), Error> {
221/// let mut pass = getpass(c"Pass: ")?;
222/// _ = pass;
223/// pass.zeroize();
224/// # Ok(())
225/// # }
226/// ```
227pub fn getpass(prompt: &CStr) -> Result<String, Error> {
228    Ok(readpassphrase_owned(
229        prompt,
230        vec![0u8; PASSWORD_LEN],
231        RppFlags::empty(),
232    )?)
233}
234
235/// An error from [`readpassphrase_owned`].
236///
237/// This wraps [`Error`] but also contains the passed buffer, accessible via [`OwnedError::take`].
238/// If not taken, the buffer is zeroed on drop.
239#[derive(Debug)]
240pub struct OwnedError(Error, Option<Vec<u8>>);
241
242/// Reads a passphrase using `readpassphrase(3)` backed by the passed buffer’s memory.
243///
244/// This function reads a passphrase of up to `buf.capacity() - 1` bytes. If the entered passphrase
245/// is longer, it will be truncated.
246///
247/// The returned [`String`] reuses `buf`’s memory; no copies are made. On error, the returned
248/// [`OwnedError`] includes the original buffer with its length reset to zero. `OwnedError`
249/// converts to [`Error`], so the `?` operator may be used with functions that return `Error`.
250///
251/// **NB**. Sometimes in Rust the capacity of a vector may be larger than you expect; if you need a
252/// precise limit on the length of the entered password, use [`readpassphrase`] instead.
253///
254/// # Security
255/// The returned `String` is owned by the caller, and it is the caller’s responsibility to clear
256/// it. This can be done via [`zeroize`], e.g.:
257/// ```no_run
258/// # use readpassphrase_3::{
259/// #     PASSWORD_LEN,
260/// #     Error,
261/// #     RppFlags,
262/// #     readpassphrase_owned,
263/// #     zeroize::Zeroize,
264/// # };
265/// # fn main() -> Result<(), Error> {
266/// let buf = vec![0u8; PASSWORD_LEN];
267/// let mut pass = readpassphrase_owned(c"Pass: ", buf, RppFlags::default())?;
268/// _ = pass;
269/// pass.zeroize();
270/// # Ok(())
271/// # }
272/// ```
273pub fn readpassphrase_owned(
274    prompt: &CStr,
275    mut buf: Vec<u8>,
276    flags: RppFlags,
277) -> Result<String, OwnedError> {
278    readpassphrase_mut(prompt, &mut buf, flags).map_err(|e| {
279        buf.clear();
280        OwnedError(e, Some(buf))
281    })
282}
283
284// Reads a passphrase into `buf`’s maybe-uninitialized capacity and returns it as a `String`
285// reusing `buf`’s memory on success. This function serves to make it possible to write
286// `readpassphrase_owned` without either pre-initializing the buffer or invoking undefined
287// behavior by constructing a maybe-uninitialized slice.
288fn readpassphrase_mut(prompt: &CStr, buf: &mut Vec<u8>, flags: RppFlags) -> Result<String, Error> {
289    unsafe {
290        let res = ffi::readpassphrase(
291            prompt.as_ptr(),
292            buf.as_mut_ptr().cast(),
293            buf.capacity(),
294            flags.bits(),
295        );
296        if res.is_null() {
297            return Err(io::Error::last_os_error().into());
298        }
299        let res = CStr::from_ptr(res).to_str()?;
300        buf.set_len(res.len());
301        Ok(String::from_utf8_unchecked(mem::take(buf)))
302    }
303}
304
305/// Securely zero the memory in `buf`.
306///
307/// This function zeroes the full capacity of `buf`, erasing any sensitive data in it. It is
308/// a simple shim for [`zeroize`] and the latter should be used instead.
309///
310/// # Usage
311/// The following are equivalent:
312/// ```no_run
313/// # use readpassphrase_3::{explicit_bzero, zeroize::Zeroize};
314/// let mut buf = vec![1u8; 1];
315/// // 1.
316/// explicit_bzero(&mut buf);
317/// // 2.
318/// buf.zeroize();
319/// ```
320#[deprecated(since = "0.6.0", note = "use zeroize::Zeroize instead")]
321pub fn explicit_bzero(buf: &mut Vec<u8>) {
322    buf.zeroize();
323}
324
325impl OwnedError {
326    /// Take `buf` out of the error.
327    ///
328    /// Returns empty [`Vec`] after the first call.
329    pub fn take(&mut self) -> Vec<u8> {
330        self.1.take().unwrap_or_default()
331    }
332}
333
334impl Drop for OwnedError {
335    fn drop(&mut self) {
336        self.1.take().as_mut().map(zeroize::Zeroize::zeroize);
337    }
338}
339
340impl From<OwnedError> for Error {
341    fn from(mut value: OwnedError) -> Self {
342        mem::replace(&mut value.0, Error::Io(io::ErrorKind::Other.into()))
343    }
344}
345
346impl From<io::Error> for Error {
347    fn from(value: io::Error) -> Self {
348        Error::Io(value)
349    }
350}
351
352impl From<Utf8Error> for Error {
353    fn from(value: Utf8Error) -> Self {
354        Error::Utf8(value)
355    }
356}
357
358impl core::error::Error for OwnedError {
359    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
360        Some(&self.0)
361    }
362}
363
364impl Display for OwnedError {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        self.0.fmt(f)
367    }
368}
369
370impl core::error::Error for Error {
371    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
372        match self {
373            Error::Io(e) => Some(e),
374            Error::Utf8(e) => Some(e),
375        }
376    }
377}
378
379impl Display for Error {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        match self {
382            Error::Io(e) => e.fmt(f),
383            Error::Utf8(e) => e.fmt(f),
384        }
385    }
386}
387
388/// A minimal in-crate implementation of [`zeroize::Zeroize`][0].
389///
390/// This provides compile-fenced memory zeroing for [`String`]s and [`Vec`]s without needing to
391/// depend on the `zeroize` crate.
392///
393/// If the optional `zeroize` feature is enabled, then the trait is replaced with a re-export of
394/// [`zeroize::Zeroize`] itself.
395///
396/// [0]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
397#[cfg(any(docsrs, not(feature = "zeroize")))]
398pub mod zeroize {
399    use std::{arch::asm, mem::MaybeUninit};
400
401    /// Trait for securely erasing values from memory.
402    pub trait Zeroize {
403        fn zeroize(&mut self);
404    }
405
406    impl Zeroize for Vec<u8> {
407        fn zeroize(&mut self) {
408            self.clear();
409            self.spare_capacity_mut().fill(MaybeUninit::zeroed());
410            compile_fence(self);
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(buf: &[u8]) {
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}