derivepassphrase._internals.cli_helpers
¶
Helper functions for the derivepassphrase command-line.
Warning
Non-public module (implementation detail), provided for didactical and educational purposes only. Subject to change without notice, including removal.
LOCK_SIZE
module-attribute
¶
LOCK_SIZE = 4096
The size of the record to lock at the beginning of the file, for locking implementations that lock byte ranges instead of whole files.
While POSIX specifies that fcntl
locks shall support a size of zero to
denote “any conceivable file size”, the locking system available in
msvcrt
does not support this, and requires an explicit size.
ConfigurationMutex
¶
ConfigurationMutex()
A mutual exclusion context manager for configuration edits.
See configuration_mutex
.
lock
instance-attribute
¶
lock: Callable[[], None] = lock_func
A function to lock the mutex exclusively.
This implementation uses a file descriptor of a well-known file,
which is opened before locking and closed after unlocking (and on
error when locking). On Windows, we use msvcrt.locking
, on
other systems, we use fcntl.flock
.
Note
This is a normal Python function, not a method.
unlock
instance-attribute
¶
unlock: Callable[[], None] = unlock_func
A function to unlock the mutex.
This implementation uses a file descriptor of a well-known file,
which is opened before locking and closed after unlocking (and on
error when locking). It will fail if the file descriptor is
unavailable. On Windows, we use msvcrt.locking
, on other
systems, we use fcntl.flock
.
Note
This is a normal Python function, not a method.
write_lock_fileobj
instance-attribute
¶
write_lock_fileobj: BinaryIO | None = None
The file object, if currently locked by this context manager.
write_lock_file
instance-attribute
¶
write_lock_file: Path = config_filename('write lock')
The filename to lock.
write_lock_condition
instance-attribute
¶
The lock protecting access to the file object.
__exit__
¶
__exit__(
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
exc_tb: TracebackType | None,
) -> Literal[False]
Exit the context, releasing the lock on the configuration file.
ORIGIN
¶
Bases: Enum
The origin of a setting, if not from the user configuration file.
Attributes:
Name | Type | Description |
---|---|---|
INTERACTIVE |
Label
|
interactive input |
shell_complete_path
¶
Request standard path completion for the path
argument.
is_completable_item
¶
Return whether the item is completable on the command-line.
The item is completable if and only if it contains no ASCII control characters (U+0000 through U+001F, and U+007F).
shell_complete_service
¶
shell_complete_service(
ctx: Context, parameter: Parameter, value: str
) -> list[str | CompletionItem]
Return known vault service names as completion items.
Service names are looked up in the vault configuration file. All
errors will be suppressed. Additionally, any service names deemed
not completable as per is_completable_item
will be silently
skipped.
configuration_mutex
¶
configuration_mutex() -> (
AbstractContextManager[AbstractContextManager]
)
Enter a mutually exclusive context for configuration writes.
Within this context, no other cooperating instance of
derivepassphrase
will attempt to write to its configuration
directory. We achieve this by locking a specific temporary file
(whose name depends on the location of the configuration directory)
for the duration of the context.
Returns:
Type | Description |
---|---|
AbstractContextManager[AbstractContextManager]
|
A reusable but not reentrant context manager, ensuring mutual
exclusion (while within its context) with all other
Upon entering the context, the context manager returns itself. |
Locking specifics
The directory for the lock file is determined via
get_tempdir
. The lock filename is
derivepassphrase-lock-<hash>.txt
, where <hash>
is computed
as follows. First, canonicalize the path to the configuration
directory with pathlib.Path.resolve
. Then encode the
result as per the filesystem encoding (os.fsencode
), and
hash it with SHA256. Finally, convert the result to standard
base32 and use the first twelve characters, in lowercase, as
<hash>
.
We use msvcrt.locking
on Windows platforms (sys.platform
== "win32"
) and fcntl.flock
on all others. All locks are
exclusive locks. If the locking system requires a byte range,
we lock the first LOCK_SIZE
bytes. For maximum
portability between locking implementations, we first open the
lock file for writing, which is sometimes necessary to lock
a file exclusively. Thus locking will fail if we lack
permission to write to an already-existing lockfile.
get_tempdir
¶
get_tempdir() -> Path
Return a suitable temporary directory.
We implement the same algorithm as tempfile.gettempdir
, except
that we default to the derivepassphrase
configuration directory
instead of the current directory if no other choice is suitable, and
that we return pathlib.Path
objects directly.
config_filename
¶
Return the filename of the configuration file for the subsystem.
The (implicit default) file is currently named settings.json
,
located within the configuration directory as determined by the
DERIVEPASSPHRASE_PATH
environment variable, or by
click.get_app_dir
in POSIX mode. Depending on the requested
subsystem, this will usually be a different file within that
directory.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
subsystem
|
str | None
|
Name of the configuration subsystem whose configuration
filename to return. If not given, return the old filename
from before the subcommand migration. If |
'old settings.json'
|
Raises:
Type | Description |
---|---|
AssertionError
|
An unknown subsystem was passed. |
Deprecated
Since v0.2.0: The implicit default subsystem and the old configuration filename are deprecated, and will be removed in v1.0. The subsystem will be mandatory to specify.
load_config
¶
load_config() -> VaultConfig
Load a vault(1)-compatible config from the application directory.
The filename is obtained via config_filename
. This must be
an unencrypted JSON file.
Returns:
Type | Description |
---|---|
VaultConfig
|
The vault settings. See |
Raises:
Type | Description |
---|---|
OSError
|
There was an OS error accessing the file. |
ValueError
|
The data loaded from the file is not a vault(1)-compatible config. |
migrate_and_load_old_config
¶
migrate_and_load_old_config() -> (
tuple[VaultConfig, OSError | None]
)
Load and migrate a vault(1)-compatible config.
The (old) filename is obtained via config_filename
. This
must be an unencrypted JSON file. After loading, the file is
migrated to the new standard filename.
Returns:
Type | Description |
---|---|
tuple[VaultConfig, OSError | None]
|
The vault settings, and an optional exception encountered during
migration. See |
Raises:
Type | Description |
---|---|
OSError
|
There was an OS error accessing the old file. |
ValueError
|
The data loaded from the file is not a vault(1)-compatible config. |
save_config
¶
save_config(config: VaultConfig) -> None
Save a vault(1)-compatible config to the application directory.
The filename is obtained via config_filename
. The config
will be stored as an unencrypted JSON file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
config
|
VaultConfig
|
vault configuration to save. |
required |
Raises:
Type | Description |
---|---|
OSError
|
There was an OS error accessing or writing the file. |
ValueError
|
The data cannot be stored as a vault(1)-compatible config. |
load_user_config
¶
Load the user config from the application directory.
The filename is obtained via config_filename
.
Returns:
Type | Description |
---|---|
dict[str, Any]
|
The user configuration, as a nested |
Raises:
Type | Description |
---|---|
OSError
|
There was an OS error accessing the file. |
ValueError
|
The data loaded from the file is not a valid configuration file. |
get_suitable_ssh_keys
¶
get_suitable_ssh_keys(
conn: SSHAgentClient | socket | None = None,
) -> Iterator[SSHKeyCommentPair]
Yield all SSH keys suitable for passphrase derivation.
Suitable SSH keys are queried from the running SSH agent (see
ssh_agent.SSHAgentClient.list_keys
).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
conn
|
SSHAgentClient | socket | None
|
An optional connection hint to the SSH agent. See
|
None
|
Yields:
Type | Description |
---|---|
SSHKeyCommentPair
|
Every SSH key from the SSH agent that is suitable for passphrase derivation. |
Raises:
Type | Description |
---|---|
KeyError
|
|
NotImplementedError
|
|
OSError
|
|
LookupError
|
No keys usable for passphrase derivation are loaded into the SSH agent. |
RuntimeError
|
There was an error communicating with the SSH agent. |
SSHAgentFailedError
|
The agent failed to supply a list of loaded keys. |
prompt_for_selection
¶
prompt_for_selection(
items: Sequence[str | bytes],
heading: str = "Possible choices:",
single_choice_prompt: str = "Confirm this choice?",
ctx: Context | None = None,
) -> int
Prompt user for a choice among the given items.
Print the heading, if any, then present the items to the user. If there are multiple items, prompt the user for a selection, validate the choice, then return the list index of the selected item. If there is only a single item, request confirmation for that item instead, and return the correct index.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
items
|
Sequence[str | bytes]
|
The list of items to choose from. |
required |
heading
|
str
|
A heading for the list of items, to print immediately before. Defaults to a reasonable standard heading. If explicitly empty, print no heading. |
'Possible choices:'
|
single_choice_prompt
|
str
|
The confirmation prompt if there is only a single possible choice. Defaults to a reasonable standard prompt. |
'Confirm this choice?'
|
ctx
|
Context | None
|
An optional |
None
|
Returns:
Type | Description |
---|---|
int
|
An index into the items sequence, indicating the user’s selection. |
Raises:
Type | Description |
---|---|
IndexError
|
The user made an invalid or empty selection, or requested an abort. |
select_ssh_key
¶
select_ssh_key(
conn: SSHAgentClient | socket | None = None,
/,
*,
ctx: Context | None = None,
) -> bytes | bytearray
Interactively select an SSH key for passphrase derivation.
Suitable SSH keys are queried from the running SSH agent (see
ssh_agent.SSHAgentClient.list_keys
), then the user is prompted
interactively (see click.prompt
) for a selection.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
conn
|
SSHAgentClient | socket | None
|
An optional connection hint to the SSH agent. See
|
None
|
ctx
|
Context | None
|
An |
None
|
Returns:
Type | Description |
---|---|
bytes | bytearray
|
The selected SSH key. |
Raises:
Type | Description |
---|---|
KeyError
|
|
NotImplementedError
|
|
OSError
|
|
IndexError
|
The user made an invalid or empty selection, or requested an abort. |
LookupError
|
No keys usable for passphrase derivation are loaded into the SSH agent. |
RuntimeError
|
There was an error communicating with the SSH agent. |
SSHAgentFailedError
|
The agent failed to supply a list of loaded keys. |
prompt_for_passphrase
¶
prompt_for_passphrase() -> str
Interactively prompt for the passphrase.
Calls click.prompt
internally. Moved into a separate function
mainly for testing/mocking purposes.
Returns:
Type | Description |
---|---|
str
|
The user input. |
check_for_misleading_passphrase
¶
check_for_misleading_passphrase(
key: tuple[str, ...] | ORIGIN,
value: Mapping[str, Any],
*,
main_config: dict[str, Any],
ctx: Context | None = None
) -> None
Check for a misleading passphrase according to user configuration.
Look up the desired Unicode normalization form in the user configuration, and if the passphrase is not normalized according to this form, issue a warning to the user.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
key
|
tuple[str, ...] | ORIGIN
|
A vault configuration key or an origin of the
value/configuration section, e.g. |
required |
value
|
Mapping[str, Any]
|
The vault configuration section maybe containing a passphrase to vet. |
required |
main_config
|
dict[str, Any]
|
The parsed main user configuration. |
required |
ctx
|
Context | None
|
The click context. This is necessary to pass output options set on the context to the logging machinery. |
None
|
Raises:
Type | Description |
---|---|
AssertionError
|
The main user configuration is invalid. |
key_to_phrase
¶
key_to_phrase(
key: str | Buffer,
/,
*,
error_callback: Callable[
..., NoReturn
] = default_error_callback,
) -> bytes
Return the equivalent master passphrase, or abort.
This wrapper around vault.Vault.phrase_from_key
emits
user-facing error messages if no equivalent master passphrase can be
obtained from the key, because this is the first point of contact
with the SSH agent.
print_config_as_sh_script
¶
print_config_as_sh_script(
config: VaultConfig,
/,
*,
outfile: TextIO,
prog_name_list: Sequence[str],
) -> None
Print the given vault configuration as a sh(1) script.
This implements the --export-as=sh
option of derivepassphrase vault
.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
config
|
VaultConfig
|
The configuration to serialize. |
required |
outfile
|
TextIO
|
A file object to write the output to. |
required |
prog_name_list
|
Sequence[str]
|
A list of (subcommand) names for the command emitting this
output, e.g. |
required |