Securely read a user password with extensible ruling and processing options, than write it asymmetrically encrypted to an output stream or file. Intended for use by other processes for secure process spawning-based IPC user password reading, hashing, or key-derivation. A simpler and easier to use `pinentry` replacement.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
Avril 8ac56c862f
Started `Error` (CXX exceptions), `alloc_t` (C/XX secure/polymorphic allocators),
2 years ago
include Started `Error` (CXX exceptions), `alloc_t` (C/XX secure/polymorphic allocators), 2 years ago
src Started `Error` (CXX exceptions), `alloc_t` (C/XX secure/polymorphic allocators), 2 years ago
.gitignore Initial commit: Outlined most project goals in `README.md` 2 years ago
Makefile Started `Error` (CXX exceptions), `alloc_t` (C/XX secure/polymorphic allocators), 2 years ago
README.md Updated README, fixed hypothetical example/small usage display. 2 years ago

README.md

readpass - Securely read a user password than write it asymmetrically encrypted to an output stream

Usage

  • readpass [OPTIONS] [<format-spec>:]<output-public-key>/-: Read user input with default prompt and hash settings, encrypt the output with <output-public-key>, and the write that to stdout (unless overridden by an OPTION, TODO: See section Options.)
  • readpass [OPTIONS] [-c] [--format[-{public,private}] <format-spec>]... keypair [<private-keyfile>]: Generate a public-private keypair and write the public key as suitable for use as <output-public-key> to stdout. (XXX: Should keypair instead be --keypair? It is a mode change, but the version, plugin-ingo, and help mode changes are all still --...) Write the private key to either <private-keyfile> if supplied, or if not, to either: if -c is not supplied and the output was not specified to be a binary format, stderr (in this case, warnings will not be omitted to stderr at all unless the program exits with non-0 code.) If -c is provided, the private key will be encoded to a text format the same as the public one and outputted as the 2nd line of stdout, warning and error reporting to stderr are not suppressed for this case. If --format is provided, the output will be in that specific format specifier (see Key formatting below.) instead of the default hex-string public key "hex:", (unless outputting to file via OPTIONS, in which case "raw:" binary), binary private key "raw:" (unless printing to stdout, in which case hex encoded private key "hex:" as well.) NOTE: The : parts of the format specifier for --format[-...] are not required and are ignored if provided in this context.
  • readpass --help: Show all modes, formatting specifiers, restriction rules, and global as well as mode-specific [OPTIONS], then exit with status 0.
  • readpass [-v] --version: Print version, license, comptime feature flags, author, build-date, build type, auto-loaded or available plugins (TODO), etc then exit with code 0. If -v is supplied, then all extra information about supported formats, supported hash and derivation functions, and information about auto-loaded or available plugins (which may have to be loaded to extract the information from if they are not converted to .rpp files.)
  • readpass [-v1n] [--oneshot] [--noload] --plugin-info [--[force-]load] [--auto] <plugin>[/]... Reads and displays (optionally verbose) plugin information from specified <plugin>s, in specified order (if a file, read the file, if a directory, use the --plugin plugin lookup rules specified in the Plugins section. The plugins may have to be loaded if they are not converted to .rpp format, to skip non-rpp format plugins that do have to be loaded, specify --noload/-n. (TODO: encrypted plugins will warrant extra options to specify which key/key-tree/keystore to use to decrypt them.) To force all to be loaded regardless, use --load. --load will fail and error-out with non-0 exit code if an encrypted plugin is specified that could not be decrypted, or if a corrupted .rpp plugin with a valid header fails to be loaded, despite the header information being already displayed before the exit. To skip past un-decryptable or corrupted plugins that fail to load, use --force-load instead. An error message will be printed for these cases and the plugin info-dump will be skipped (unless -v is provided, and it is the valid-header situation, in which case that header information is still printed.)

If --auto is applied, all plugins detected for auto-load are added to the start of the <plugin>... list in the order they appear in normal usage.

The plugins will be unloaded in reverse order before the program exists, after the info-dump, regardless of if the program is exiting in error. To unload the plugins after their invididual info-dump instead, specify --oneshot/-1.

Key formatting

See the Example section for prefix-encoding of hashes. Without and explicit <format-spec> prefix, the <output-public-key> will attempt to be decoded as such:

  • as a hex-string encoded key
  • as a base64-string encoded key
  • as raw binary data (if format auto-detection is enabled and falls back to this, a warning will be omitted.) Or, if [<format-spec>:]- is provided (and does not conflict with any [OPTIONS] flag specifying to read user password from stdin instead of prompting, for example,) the stdin buffer is read and interpreted as raw binary data unless explicit <format-spec> is prefixed, in which case that is used.

If any explicit <format-spec> fails to parse the input, the program will fail with a non-0 exit code and error message.

Valid format specifiers for <format-spec> are:

  • hex: Interpret the string as encoded as a hex-string
  • base64: Interpret the string as encoded as a base64-string
  • raw: Interpret the string as the raw key bytes
  • file[/<file-format-spec>]: Read the key from a file, with an optional format specifier for the data read from the file after a / but before the :, otherwise, it is defaulted to assume raw binary key data.

Implicit vs. explicit (<format-spec>) format reduction rules.

All implicit decodes may issue warnings (if stderr is not used as an output stream) if data appears to be assumed incorrectly encoded, or if the distance from the string and a valid representation of it for the current format being tested is low enough. (i.e. the amount of error present in an encoding. 01bg has an error distance of just 1, and will be warned as "Did you mean hex:01bf?", whereas cafeworld will not be warned on for the hex stage, despite cafe being valid, as its error disance is so large it is impossible to be just a typo.) Base64 strings may ommit the ending == iff they are both: explicity specified (base64 <format-spec>) and the expected input size is fixed to always make them redundant. Otherwise if the latter holds but the former does not, a warning of "This looks like a trimmed base64 string, did you mean base64:...? (Note that implicit encoding deductions are not allowed to trim redundancy when the sized is fixed to cause it, you must still explicitly specify the format for this case if that was your intent.)"

It is advised to always use explicit format specifiers wherever possible if your input is in any way non-trivial.

See section Format speficication (TODO) for more details.

Plugins

TODO: Plugins are dynamic objects can be loaded at runtime using a provided plugin API, they can provide --restrict rules, --{post,pre}process directives, additional available hashing and derivation functions, and additional format-spec explicit specifiers, implicit ordering steps, and decoding algorithms. There is probably more that can be done with them.

TODO: Enable plugin autoload? Maybe make this a compilation feature and if enabled give it a disabling flag in [OPTIONS].

TODO: Explicit plugin load: --plugin {<plugin-file>,<plugin-directory>/}: Plugin loads happen before any other directives, the also happen in the order specified on the command-line. If the argument to --plugin is a directory, the directory is enumerated over (default Unix file ordering) and each valid plugin (or file with any extension of: .so.rpp, .rpp) is decoded (if needed) and then loaded.

TODO: Building a plugin example. compiling one, and encoding one. It is optional for the .so plugin to be converted to a valid .rpp plugin file just by renaming it, but it can also be encoded as such:

  • zstd (XXX: or maybe other supported algos) compressed .so file.
  • Optionally encrypted and/or signed. (XXX: with our own key formats? Or with gpg ones or something? I think with our own would be better, we don't want required runtime dependencies, especially not gpg itself...)
  • prepended with an rpp header showing information about the plugin that can be read without loading the library itself. (such as compression, cryptographic signing of the plugin binary itself, information about the decryption needed of encrypted plugins, etc.)

Example

$ readpass --prompt "Please enter password" \		# Initial user prompt
	--restrict size 10..50  \			# Restrict on a Rust-format size range:`10..50` >=10 && <50, `10..` >=10, `..` unrestricted, `..=50` <=50 (no lower bound, can be zero chars here) Or a specific exact size (e.g: `10` == 10)
	--restrict regex '(?!.*\2)(?:(?1){4})+$' \ 	# Restrict based on regex match (this one requires at least 4 unique characters in a row at the end of the password.
	--restrict deny-list sha256 wordlist.txt salt \
						\	# Deny all passwords who's sha256 hash  (with user-provided `salt`)
						\	# The salt can be one of the following: `raw:salt_string` (use the literal "salt_string". "hex:<value hex-string-encoded data>" Where the post-colon text is decoded into bytes as a hexadecimal string, e.g: "hex:cafebeef1004" is decoded to the hex-dump literal `0xcafebeef1004`. "base64:<base64 string>" Where the post-colon text is a valid base64 string, the salt used is the basae64 decoded data. "file:<filename>[/{[{=,-}],..}<bytes>]" use the data of a file as the salt, optionally only up to "<bytes>" number read, omit the `..` to read only exactly `<bytes>` number from the file, or if the range has a lower bound, the most possible number of bytes within that range; the `=` is entirely optional, but explicitly says to read the *most* bytes, if the character `-` is used instead of the `=` before the range specifier, then it will read the *least* bytes instead. "fd:<fd>[{{=,-},..}<bytes>]" specify a currently open file-descriptor to read the salt from (will be read to the end if not lstat()/mmap()able). The optional sizing restriction rules are the same as with `file:`, but the `=` is not optional and must be provided if a `-` is not (see "file:" sizing behaviour.). Essentially the same as`file:/dev/<fd>[/sizing-rules...]. )
						\	# Or a bare string literal, which defaults to assuming the prefix is "raw:".
						\	# (NOTE: a "did you mean" warning _may_ be omitted if the data string starts with a prefix "<kw>:", and the levelshtein distance between <kw> and a valid keyword is very low, so explicit "raw:" is advised over letting the implicit default behaviour potentially emit these kinds of warnings.)
						\	# Valid options for `sha256` are: ( Salt required: `sha256`, `sha512`, `sha1`, `blake2`.
						\	# The hashes will be attempted to be read as such from the wordlist: optionally (since hashes are of fixed size) whitespace-delimited hex strings decoded, optionally whitespace-delimited base64 strings, raw binary datawith no delimiter. A warning *may* be omitted if it is thought the file was *intended* to be read as a line/whitespace-based format when it is instead being read as a binary one. To avoid any potential program confusion, the format may be specified explicitly by prefixing `wordlist` with any of: ("hex[=]:[:]", "base64[=]:[:]", "raw[=]:[:]", specifying the format. The `=` character additionally specifies that the file is *not delimited*, i.e. the fixed-sized hash list is to be read one fixed-size encoding at a time; for "raw:", this prevents auto-decompression of detected-compressed files, and the postfix extra `:` redundantly has the same behaviour.) 
						\	# The file may be compressed with: `zstd`, `brotli`, or `gz` algorithms; if the header of the file indicates that is compressed with that format, it will first be decompressed. If the file seems compressed, but still can be validly interpreted in the specified (or inferred) format without decompression, a warning *will* be outputted. To prevent this, post-fix the ":" with another `:` on the explicit format specifier to disable automatic attempted decompression (for "raw=:", the second ":" is redundant, but either/or can still be specified.) 
						\	# Some hashes may require size constraints on the salt, if the ":" of a specifier is prefixed with the character `~` (e.g. `raw~:`, `file~:`), then if the size of the read salt is lower than what is required, a correctly-sized nonce will *instead* be generated from the read salt using as many rounds of the `blake2` hash (/*XXX: is this still secure? should we choose an embedded salt for this at compile-time, or blake2 the `salt` specifier list and use that result instead? It cannot be random, since the hashes much match*/ unsalted) on the input needed to generate a nonce of the required size (usually, only one round will be applied to generate enough data to use, when reading a specific size, if the input is too large, the rest will just be discarded, the extra hashing round is only for inputs that are too small.)
						\	# Salt not required: `raw` (i.e, deny raw password strings delimited by newlines inside the wordlist.txt), `null`, same as raw, but delimited by the NUL character instead of newlines.
	--restrict deny sha256 word salt	\	# The same as above, but a single value specified on the command-line instead of a file containing a list of them, the same formatting rules for `deny-list` can be applied to "word" as can be "wordlist", and are interpreted the same, without the compression support or the `=` and `::` modofiers. The default for hashes (like `sha256`) is "hex:", and auto-detection is applied to validly encoded inputs. The default for `raw` is "raw:", since it is a raw string match and a password is likely not to contain encoded binary data, the explicit modifier can still be used for `raw` though, but by default it is interperted verbatim.
	--restrict allow-list ...		\	# See `deny-list`. This has the same arguments, and whitelists overrides restrictions on passwords denied by *previous* `deny[-...]` `--restrict` directives, but not subsequent ones.
	--restrict allow ...			\	# See `deny`. This has the same arguments. Behaves the same as `allow-list` but for a single value.
	--restrict deny-all			\	# Blacklist overrides all restriction exceptions places on passwords by any *subsequent* `allow[-...]` --restrict directives. (See `allow[-...]`.)
	--restrict allow-all			\	# Override and whitelist every *subsequent* `deny[-...]` --restrict directives. (See `deny[-...]`.)
	--restrict whitelist ...		\	# The same as `--restrict deny-all --restrict allow-list ...` (See restriction directive ordering rules for `allow/deny[-...]` directives. (TODO: explain them on completeness in another section of the README.md file.)
	--restrict whitelist-single ...		\	# The same as `--restrict deny-all --restrict allow ...`
	--restrict plugin:<loaded-plugin-name> ... \	# Add a restriction rule from an externally loaded plugin (TODO: see Plugins section of README.md file)
	--process <algo> [salt]	[...[;]]	\	# Postprocess the password *before* encrypting it with the `<output-public-key>`, applying a supported algo (or available loaded "plugin:[<plugin-ident>:]<algo>".) Supported algos are: Compression: `zstd`, `gz`, plugin-based, etc. Derivation (requires size argument after `salt`): `pbkdf2`, `argon2[i][d]`, `blake2`, plugin based etc. Hashing: All available hashing algorithms specified above in `deny-list` (which also includes plugin-based ones). Encryption: `[x]chacha20`, `[x]salsa`. Signing: `sign-<hash-algo>`, the signing key must be provided after `salt`. The signature will be appended to the data in the process chain.
						\	# As well as format specifier changes ("hex", "base64", plugin-specific etc.). Salt specifying rules are the same as described by `deny-list` as well if a salt or nonce is required; or can be explicitly `none:`, the format-spec that just means "0 bytes of nothing, regardless of what is after the `:`".
						\	# `--process` directives are applied in the order specified, so `--process zstd 11 \;  --process argon2id hex:... [size=]255 lanes=4 \; --process sha256 none:` will: Compress the password with zstd compression level 11, then run the compressed data through argon2id with 4 lanes for 255 derived bytes with the specified nonce in hex format, then hash those 255 bytes with a saltless sha256, and *that* hash will be passed to the asymmetric encryption with `<output-public-key>`.
	--preprocess <algo> [salt] [...[;]]	\	# Same behaviour and rules as `--process`, except the process happens *after* the asymmetric encryption.
	--raw-input				\	# Do not treat terminal key-combinations such as U^ as they should be, but insert the keystroke in to the input directly
	--show-length[=<char>]			\	# Instead of printing nothing to the screen as the user is typing, the prompt should show the user the length of the string by outputting `<char>` for each new character read (and removing them when removed.) The default `<char>` is '*'.
	hex:... > encrypted-password.socket	# Specify `<output-public-key>` as hex-encoded, and write the output to a UNIX socket, which another process that created the socket and has the private key safely stored in its own memory can then read from and decrypt to get the user's provided password

#	--restrict [plugin-namespace-identifier:]<rule-name> <options...> [;]			<- the format for `--restrict` directives, it is the same as the `--process` ones (below), except it acts on the rule list and reads from the available rule operations table instead of the [post]process list and available operations table. The ';' semicolon terminator is usually not needed, but can be. 
# 												Rules may take fixed, variable, positional, and named arguments, the same as processes may take, (though they use variable ones far less often in general.) If the argument list is not fixed or contextually fixed, then the ';' will be required, if it is only contextually fixed, then the ';' isn't *required*, but might clarify your intentions to the program a bit better.
#	--[pre]process [plugin-namespace-identifier:] <process-name> [options...] [;]		<- the format for `--[pre]process` directives, which can have named and positional arguments of a potentially variable length, to explicitly terminate the arguments being passed to this directive, have an argument that is the ';' character.