readpassphrase_3/
lib.rs

1//! Lightweight, easy-to-use wrapper around the C [`readpassphrase(3)`][0] function.
2//!
3//! From the man page:
4//! > The `readpassphrase()` function displays a prompt to, and reads in a passphrase from,
5//! > `/dev/tty`. If this file is inaccessible and the [`RPP_REQUIRE_TTY`](Flags::REQUIRE_TTY) flag
6//! > is not set, `readpassphrase()` displays the prompt on the standard error output and reads
7//! > from the standard input.
8//!
9//! # Usage
10//! For the simplest of cases, where you would just like to read a password from the console into a
11//! [`String`] to use elsewhere, you can use [`getpass`]:
12//! ```no_run
13//! use readpassphrase_3::getpass;
14//! let _ = getpass(c"Enter your password: ").expect("failed reading password");
15//! ```
16//!
17//! If you need to pass [`Flags`] or to control the buffer size, then you can use
18//! [`readpassphrase`] or [`readpassphrase_into`] depending on your ownership requirements:
19//! ```no_run
20//! let mut buf = vec![0u8; 256];
21//! use readpassphrase_3::{Flags, readpassphrase};
22//! let pass: &str = readpassphrase(c"Password: ", &mut buf, Flags::default()).unwrap();
23//!
24//! use readpassphrase_3::readpassphrase_into;
25//! let pass: String = readpassphrase_into(c"Pass: ", buf, Flags::FORCELOWER).unwrap();
26//! # _ = pass;
27//! ```
28//!
29//! # Security
30//! The [`readpassphrase(3)` man page][0] says:
31//! > The calling process should zero the passphrase as soon as possible to avoid leaving the
32//! > cleartext passphrase visible in the process's address space.
33//!
34//! It is your job to ensure that this is done with the data you own, i.e.
35//! any [`Vec`] passed to [`readpassphrase`] or any [`String`] received from [`getpass`] or
36//! [`readpassphrase_into`].
37//!
38//! This crate ships with a minimal [`Zeroize`] trait that may be used for this purpose:
39//! ```no_run
40//! # use readpassphrase_3::{Flags, getpass, readpassphrase, readpassphrase_into};
41//! use readpassphrase_3::Zeroize;
42//! let mut pass = getpass(c"password: ").unwrap();
43//! // do_something_with(&pass);
44//! pass.zeroize();
45//!
46//! let mut buf = vec![0u8; 256];
47//! let res = readpassphrase(c"password: ", &mut buf, Flags::empty());
48//! // match_something_on(res);
49//! buf.zeroize();
50//!
51//! let mut pass = readpassphrase_into(c"password: ", buf, Flags::empty()).unwrap();
52//! // do_something_with(&pass);
53//! pass.zeroize();
54//! ```
55//!
56//! ## Zeroizing memory
57//! This crate works well with the [`zeroize`] crate. For example, [`zeroize::Zeroizing`] may be
58//! used to zero buffer contents regardless of a function’s control flow:
59//! ```no_run
60//! # use readpassphrase_3::{Error, Flags, PASSWORD_LEN, getpass, readpassphrase};
61//! use zeroize::Zeroizing;
62//! # fn main() -> Result<(), Error> {
63//! let mut buf = Zeroizing::new(vec![0u8; PASSWORD_LEN]);
64//! let pass = readpassphrase(c"pass: ", &mut buf, Flags::REQUIRE_TTY)?;
65//! // do_something_that_can_fail_with(pass)?;
66//!
67//! // Or alternatively:
68//! let pass = Zeroizing::new(getpass(c"pass: ")?);
69//! // do_something_that_can_fail_with(&pass)?;
70//! # Ok(())
71//! # }
72//! ```
73//!
74//! If this crate’s `zeroize` feature is enabled, then its [`Zeroize`] will be replaced by a
75//! re-export of the upstream [`zeroize::Zeroize`].
76//!
77//! # “Mismatched types” errors
78//! The prompt strings in this API are <code>&[CStr]</code>, not <code>&[str]</code>.
79//! This is because the underlying C function assumes that the prompt is a NUL-terminated string;
80//! were we to take `&str` instead of `&CStr`, we would need to make a copy of the prompt on every
81//! call.
82//!
83//! Most of the time, your prompts will be string literals; you can ask Rust to give you a `&CStr`
84//! literal by simply prepending `c` to the string:
85//! ```no_run
86//! # use readpassphrase_3::{Error, getpass};
87//! # fn main() -> Result<(), Error> {
88//! let _ = getpass(c"pass: ")?;
89//! //              ^
90//! //              |
91//! //              like this
92//! # Ok(())
93//! # }
94//! ```
95//!
96//! If you need a dynamic prompt, look at [`CString`](std::ffi::CString).
97//!
98//! # Windows Limitations
99//! The Windows implementation of `readpassphrase(3)` that we are using does not yet support UTF-8
100//! in prompts; they must be ASCII. It also does not yet support flags, and always behaves as
101//! though called with [`Flags::empty()`].
102//!
103//! [0]: https://man.openbsd.org/readpassphrase
104//! [str]: prim@str "str"
105
106use std::{cmp, error, ffi::CStr, fmt, io, mem, str};
107
108use bitflags::bitflags;
109#[cfg(any(docsrs, not(feature = "zeroize")))]
110pub use our_zeroize::Zeroize;
111#[cfg(all(not(docsrs), feature = "zeroize"))]
112pub use zeroize::Zeroize;
113
114/// Size of buffer used in [`getpass`].
115///
116/// Because `readpassphrase(3)` NUL-terminates its string, the actual maximum password length for
117/// [`getpass`] is 255.
118pub const PASSWORD_LEN: usize = 256;
119
120/// Maximum capacity to use with [`readpassphrase_into`].
121///
122/// Vectors with allocations larger than this will only have their capacity used up to this limit.
123/// (The initialized portion of the input vector is always used regardless of size.)
124pub const MAX_CAPACITY: usize = 1 << 12;
125
126bitflags! {
127    /// Flags for controlling readpassphrase.
128    ///
129    /// The default flag `ECHO_OFF` is not represented here because `bitflags` [recommends against
130    /// zero-bit flags][0]; it may be specified as either [`Flags::empty()`] or
131    /// [`Flags::default()`].
132    ///
133    /// Note that the Windows `readpassphrase(3)` implementation always acts like it has been
134    /// passed `ECHO_OFF`, i.e., the flags are ignored.
135    ///
136    /// [0]: https://docs.rs/bitflags/latest/bitflags/#zero-bit-flags
137    #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
138    pub struct Flags: i32 {
139        /// Leave echo on.
140        const ECHO_ON     = 0x01;
141        /// Fail if there is no tty.
142        const REQUIRE_TTY = 0x02;
143        /// Force input to lower case.
144        const FORCELOWER  = 0x04;
145        /// Force input to upper case.
146        const FORCEUPPER  = 0x08;
147        /// Strip the high bit from input.
148        const SEVENBIT    = 0x10;
149        /// Read from stdin, not `/dev/tty`.
150        const STDIN       = 0x20;
151    }
152}
153
154/// Errors that can occur in readpassphrase.
155#[derive(Debug)]
156pub enum Error {
157    /// `readpassphrase(3)` itself encountered an error.
158    Io(io::Error),
159    /// The entered password was not UTF-8.
160    Utf8(str::Utf8Error),
161}
162
163/// Reads a passphrase using `readpassphrase(3)`.
164///
165/// This function returns a <code>&[str]</code> backed by `buf`, representing a password of up to
166/// `buf.len() - 1` bytes. Any additional characters and the terminating newline are discarded.
167///
168/// # Errors
169/// Returns [`Err`] if `readpassphrase(3)` itself failed or if the entered password is not UTF-8.
170/// The former will be represented by [`Error::Io`] and the latter by [`Error::Utf8`].
171///
172/// # Security
173/// The passed buffer might contain sensitive data, even if this function returns an error.
174/// Therefore it should be zeroed as soon as possible. This can be achieved, for example, with
175/// [`zeroize::Zeroizing`]:
176/// ```no_run
177/// # use readpassphrase_3::{PASSWORD_LEN, Error, Flags, readpassphrase};
178/// use zeroize::Zeroizing;
179/// # fn main() -> Result<(), Error> {
180/// let mut buf = Zeroizing::new(vec![0u8; PASSWORD_LEN]);
181/// let pass = readpassphrase(c"Pass: ", &mut buf, Flags::default())?;
182/// # Ok(())
183/// # }
184/// ```
185/// [str]: prim@str "str"
186pub fn readpassphrase<'a>(
187    prompt: &CStr,
188    buf: &'a mut [u8],
189    flags: Flags,
190) -> Result<&'a str, Error> {
191    #[cfg(debug_assertions)]
192    {
193        // Fill `buf` with nonzero bytes to check that `ffi::readpassphrase` wrote a NUL.
194        buf.fill(1);
195    }
196    let prompt = prompt.as_ptr();
197    let buf_ptr = buf.as_mut_ptr().cast();
198    let bufsiz = buf.len();
199    let flags = flags.bits();
200    // SAFETY: `prompt` is a NUL-terminated byte sequence, and `buf_ptr` is an allocation of at
201    // least `bufsiz` bytes, by construction from `&CStr` and `&mut [u8]` respectively.
202    let res = unsafe { ffi::readpassphrase(prompt, buf_ptr, bufsiz, flags) };
203    if res.is_null() {
204        return Err(io::Error::last_os_error().into());
205    }
206    Ok(CStr::from_bytes_until_nul(buf).unwrap().to_str()?)
207}
208
209/// Reads a passphrase using `readpassphrase(3)`, returning a [`String`].
210///
211/// Internally, this function uses a buffer of [`PASSWORD_LEN`] bytes, allowing for passwords up to
212/// `PASSWORD_LEN - 1` characters (accounting for the C NUL terminator.) Any additional characters
213/// and the terminating newline are discarded.
214///
215/// # Errors
216/// Returns [`Err`] if `readpassphrase(3)` itself failed or if the entered password is not UTF-8.
217/// The former will be represented by [`Error::Io`] and the latter by [`Error::Utf8`].
218///
219/// # Security
220/// The returned `String` is owned by the caller, and therefore it is the caller’s responsibility
221/// to clear it when you are done with it:
222/// ```no_run
223/// # use readpassphrase_3::{Error, Zeroize, getpass};
224/// # fn main() -> Result<(), Error> {
225/// let mut pass = getpass(c"Pass: ")?;
226/// _ = pass;
227/// pass.zeroize();
228/// # Ok(())
229/// # }
230/// ```
231pub fn getpass(prompt: &CStr) -> Result<String, Error> {
232    let buf = Vec::with_capacity(PASSWORD_LEN);
233    Ok(readpassphrase_into(prompt, buf, Flags::empty())?)
234}
235
236/// An [`Error`] from [`readpassphrase_into`] containing the passed buffer.
237///
238/// The buffer is accessible via [`IntoError::into_bytes`][0], and the `Error` via
239/// [`IntoError::error`].
240///
241/// If [`into_bytes`][0] is not called, the buffer is automatically zeroed on drop.
242///
243/// [0]: IntoError::into_bytes
244#[derive(Debug)]
245pub struct IntoError(Error, Option<Vec<u8>>);
246
247/// Reads a passphrase using `readpassphrase(3)`, returning `buf` as a [`String`].
248///
249/// The returned [`String`] reuses `buf`’s memory; no copies are made, and `buf` is never
250/// reallocated.
251///
252/// `buf`’s full allocation will be  used, whether initialized or not, up to [4KiB][MAX_CAPACITY];
253/// i.e., the following two statements are equivalent:
254/// ```no_run
255/// # use readpassphrase_3::{Flags, readpassphrase_into};
256/// # let flags = Flags::empty();
257/// let _ = readpassphrase_into(c"> ", vec![0u8; 128], flags);
258/// let _ = readpassphrase_into(c"> ", Vec::with_capacity(128), flags);
259/// ```
260///
261/// If for some reason you must use this function to read more than 4KiB of text, then you should
262/// initialize the buffer to the length you will need.
263///
264/// # Errors
265/// Returns [`Err`] if `readpassphrase(3)` itself failed or if the entered password is not UTF-8.
266/// The former will be represented by [`Error::Io`] and the latter by [`Error::Utf8`]. The vector
267/// you moved in is also included, and in the case of [`Error::Utf8`], contains the non-UTF8 byte
268/// sequence produced by `readpassphrase(3)`.
269///
270/// See the docs for [`IntoError`] for more details on what you can do with this error.
271///
272/// # Security
273/// The returned `String` is owned by the caller, and it is the caller’s responsibility to clear
274/// it. This can be done via [`Zeroize`], e.g.:
275/// ```no_run
276/// # use readpassphrase_3::{
277/// #     PASSWORD_LEN,
278/// #     Error,
279/// #     Flags,
280/// #     readpassphrase_into,
281/// # };
282/// # use readpassphrase_3::Zeroize;
283/// # fn main() -> Result<(), Error> {
284/// let buf = vec![0u8; PASSWORD_LEN];
285/// let mut pass = readpassphrase_into(c"Pass: ", buf, Flags::default())?;
286/// _ = pass;
287/// pass.zeroize();
288/// # Ok(())
289/// # }
290/// ```
291pub fn readpassphrase_into(
292    prompt: &CStr,
293    mut buf: Vec<u8>,
294    flags: Flags,
295) -> Result<String, IntoError> {
296    let bufsiz = cmp::max(buf.len(), cmp::min(buf.capacity(), MAX_CAPACITY));
297    if cfg!(debug_assertions) {
298        // Fill `buf` with nonzero bytes to check that `ffi::readpassphrase` wrote a NUL.
299        buf.fill(1);
300        buf.resize(bufsiz, 1);
301    } else {
302        buf.resize(bufsiz, 0);
303    }
304    let prompt = prompt.as_ptr();
305    let buf_ptr = buf.as_mut_ptr().cast();
306    let flags = flags.bits();
307    // SAFETY: By construction as with `readpassphrase` above.
308    let res = unsafe { ffi::readpassphrase(prompt, buf_ptr, bufsiz, flags) };
309    if res.is_null() {
310        buf.clear();
311        return Err(IntoError(io::Error::last_os_error().into(), Some(buf)));
312    }
313    let len = buf.iter().position(|&b| b == 0).unwrap();
314    buf.truncate(len);
315    String::from_utf8(buf).map_err(|e| {
316        let err = e.utf8_error();
317        let buf = e.into_bytes();
318        IntoError(Error::Utf8(err), Some(buf))
319    })
320}
321
322impl IntoError {
323    /// Return the [`Error`] corresponding to this.
324    pub fn error(&self) -> &Error {
325        &self.0
326    }
327
328    /// Returns the buffer that was passed to [`readpassphrase_into`].
329    ///
330    /// # Security
331    /// The returned buffer may contain sensitive data in its spare capacity, even if the
332    /// buffer’s length is zero. It is the caller’s responsibility to zero it as soon as possible
333    /// if needed, e.g. using [`Zeroize`]:
334    /// ```no_run
335    /// # use std::io::*;
336    /// # use readpassphrase_3::{Flags, Zeroize, readpassphrase_into};
337    /// # let err = readpassphrase_into(c"", vec![], Flags::empty()).unwrap_err();
338    /// let mut buf = err.into_bytes();
339    /// // ...
340    /// buf.zeroize();
341    /// ```
342    pub fn into_bytes(mut self) -> Vec<u8> {
343        self.1.take().unwrap()
344    }
345}
346
347impl error::Error for IntoError {
348    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
349        Some(&self.0)
350    }
351}
352
353impl fmt::Display for IntoError {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        self.0.fmt(f)
356    }
357}
358
359impl Drop for IntoError {
360    fn drop(&mut self) {
361        if let Some(mut buf) = self.1.take() {
362            buf.zeroize();
363        }
364    }
365}
366
367impl From<IntoError> for Error {
368    fn from(mut value: IntoError) -> Self {
369        mem::replace(&mut value.0, Error::Io(io::ErrorKind::Other.into()))
370    }
371}
372
373impl From<io::Error> for Error {
374    fn from(value: io::Error) -> Self {
375        Error::Io(value)
376    }
377}
378
379impl From<str::Utf8Error> for Error {
380    fn from(value: str::Utf8Error) -> Self {
381        Error::Utf8(value)
382    }
383}
384
385impl error::Error for Error {
386    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
387        Some(match self {
388            Error::Io(e) => e,
389            Error::Utf8(e) => e,
390        })
391    }
392}
393
394impl fmt::Display for Error {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        match self {
397            Error::Io(e) => e.fmt(f),
398            Error::Utf8(e) => e.fmt(f),
399        }
400    }
401}
402
403#[cfg(any(docsrs, not(feature = "zeroize")))]
404mod our_zeroize {
405    use std::{arch::asm, mem::MaybeUninit};
406
407    /// A minimal in-crate implementation of a subset of [`zeroize::Zeroize`].
408    ///
409    /// This provides compile-fenced memory zeroing for [`String`]s and [`Vec`]s without needing to
410    /// depend on the `zeroize` crate.
411    ///
412    /// If the optional `zeroize` feature is enabled, then the trait is replaced with a re-export of
413    /// `zeroize::Zeroize` itself.
414    pub trait Zeroize {
415        fn zeroize(&mut self);
416    }
417
418    impl Zeroize for Vec<u8> {
419        fn zeroize(&mut self) {
420            self.clear();
421            let buf = self.spare_capacity_mut();
422            buf.fill(MaybeUninit::zeroed());
423            compile_fence(buf);
424        }
425    }
426
427    impl Zeroize for String {
428        fn zeroize(&mut self) {
429            // SAFETY: we clear the string.
430            unsafe { self.as_mut_vec() }.zeroize();
431        }
432    }
433
434    impl Zeroize for [u8] {
435        fn zeroize(&mut self) {
436            self.fill(0);
437            compile_fence(self);
438        }
439    }
440
441    fn compile_fence<T>(buf: &[T]) {
442        unsafe {
443            asm!(
444                "/* {ptr} */",
445                ptr = in(reg) buf.as_ptr(),
446                options(nostack, preserves_flags, readonly)
447            );
448        }
449    }
450}
451
452#[cfg(use_tcm)]
453use tcm_readpassphrase_vendored as ffi;
454#[cfg(not(use_tcm))]
455mod ffi {
456    use std::ffi::{c_char, c_int};
457
458    extern "C" {
459        pub(crate) fn readpassphrase(
460            prompt: *const c_char,
461            buf: *mut c_char,
462            bufsiz: usize,
463            flags: c_int,
464        ) -> *mut c_char;
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471
472    #[test]
473    fn test_empty() {
474        let err = readpassphrase_into(c"pass", Vec::new(), Flags::empty()).unwrap_err();
475        let Error::Io(err) = err.into() else {
476            panic!();
477        };
478        #[cfg(not(windows))]
479        assert_eq!(io::ErrorKind::InvalidInput, err.kind());
480        #[cfg(windows)]
481        {
482            _ = err
483        };
484
485        let mut buf = Vec::new();
486        let err = readpassphrase(c"pass", &mut buf, Flags::empty()).unwrap_err();
487        let Error::Io(err) = err else {
488            panic!();
489        };
490        #[cfg(not(windows))]
491        assert_eq!(io::ErrorKind::InvalidInput, err.kind());
492        #[cfg(windows)]
493        {
494            _ = err
495        };
496    }
497
498    #[test]
499    fn test_zeroize() {
500        let mut buf = "test".to_string();
501        buf.zeroize();
502        assert_eq!(0, buf.len());
503        unsafe { buf.as_mut_vec().set_len(4) };
504        assert_eq!("\0\0\0\0", &buf);
505        let mut buf = vec![1u8; 15];
506        unsafe { buf.set_len(0) };
507        let x = buf.spare_capacity_mut()[0];
508        assert_eq!(unsafe { x.assume_init() }, 1);
509        buf.zeroize();
510        unsafe { buf.set_len(15) };
511        assert_eq!(vec![0u8; 15], buf);
512        let mut buf = vec![1u8; 2];
513        unsafe { buf.set_len(1) };
514        let slice = &mut *buf;
515        slice.zeroize();
516        unsafe { buf.set_len(2) };
517        assert_eq!(vec![0u8, 1], buf);
518    }
519}