Internet-Draft | OpenPGP Prompting and Caching | August 2024 |
Gillmor | Expires 9 February 2025 | [Page] |
Some OpenPGP secret keys and messages are locked with a passphrase. An OpenPGP-using application might want to prompt the user for the passphrase, or might want to permit the user to only enter the passphrase once while keeping the material unlocked for future use. This document describes a simple interface that can be used for prompting the user and for caching the results of such a prompt. It is designed to interoperate well with the Stateless OpenPGP Interface, and to facilitate its use both in test suite operations and in common patterns of interactive operation.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://dkg.gitlab.io/openpgp-prompting-caching/. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-dkg-openpgp-prompting-caching/.¶
Discussion of this document takes place on the OpenPGP Working Group mailing list (mailto:openpgp@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/openpgp/. Subscribe at https://www.ietf.org/mailman/listinfo/openpgp/.¶
Source for this draft and an issue tracker can be found at https://gitlab.com/dkg/openpgp-prompting-caching/.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 9 February 2025.¶
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Some OpenPGP secret keys and messages are locked with a passphrase.¶
An application that uses OpenPGP might want to prompt the user for the passphrase, or might want to permit the user to only enter the passphrase once while keeping the material unlocked for future use.¶
This document describes a simple interface that can be used for prompting the user and for caching the results of such a prompt across an interactive session.
The OpenPGP Prompting and Caching interface described here can be referred to as OPAC.
A specific command-line utility offering this interface is referred to as opac
.¶
It is intended to interoperate well with the Stateless OpenPGP Interface (see [I-D.dkg-openpgp-stateless-cli]), and to facilitate the use of OpenPGP both in test suite operations and in common patterns of interactive operation.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document tries to use certain terms with specificity:¶
"Certificate" refers to an OpenPGP Certificate, or Transferable Public Key, as defined in [RFC9580]¶
"Secret Key" refers to an OpenPGP Transferable Secret Key, as defined in [RFC9580].¶
"S2K" refers to the OpenPGP String-to-Key process for deriving high-quality cryptographic symmetric keys from a human-memorable string of text, also defined in [RFC9580].¶
"Session" refers to an interactive user session at a computer. Different human-computer interfaces may have different conceptions of a session, and this document is largely agnostic about those details. The salient features of a "session", for the purposes of this document, are that it has a defined time limit (often bounded by operations like "logging in" and "logging out"), a way of requesting and gathering feedback from the user ("prompting"), and is intentionally under some overarching administrative authority or control by the system operator ("the logged in user").¶
"Fails with XXX" means that the invoked command terminates and yields a specific value associated with XXX (see Table 1) as its return code.¶
OPAC is a command-line interface to facilitate the use of OpenPGP, that binds together three things:¶
The user's interactive session.¶
An ephemeral cache of OpenPGP-related data that can "unlock" OpenPGP objects in the filesystem.¶
A session-specific way to notify the user or elicit feedback from them ("prompting").¶
The ephemeral cache may be backed by a variety of different mechanisms. For example, it could use memory of an ephemeral session-bound process, or it could store data in a kernel-level keyring. Critically, the cache lasts no more than the duration of the user's session, and the material in the cache is never deliberately placed in non-ephemeral storage.¶
Since the cached material is associated with objects in the filesystem, OPAC tends to assume that all processes in the session will have the same view of the filesystem as each other, or at the very least that any process invoking OPAC will have the same view as any session-bound process that backs the OPAC cache.¶
There are unusual computing scenarios or configurations where OPAC will not work correctly, but it is intended to work with the overwhelming majority of interactive computing environments. See Section 7 for more details about OPAC's applicability.¶
opac
uses a command-line interface, with a set of standardized subcommands.¶
opac version [--extended|--opac]¶
This subcommand emits version information as UTF-8-encoded text.¶
With no arguments, the version string emitted should contain the name of the opac
implementation, followed by a single space, followed by the version number.
An opac
implementation should use a version number that respects an established standard that is easily comparable and parsable, like [SEMVER].¶
If --extended
is supplied, the implementation may emit multiple lines of version information.
The first line MUST match the information produced by a simple invocation, but the rest of the text has no defined structure.¶
If --opac
is supplied, the implementations should produce a single line with the implemented [SEMVER] opac
interface that this implementation specifies.
For this draft, that version is 0.1
.¶
Example:¶
$ opac version ExampleOpac 0.3 $ opac version --extended ExampleOpac 0.3 LibDBus 0.11.4 See https://pgp.example/opac/ for more information $ opac version --opac 0.1 $¶
opac get-password [--message MESSAGE] [--ignore-cache] [--prompt-timeout TIMEOUT] FILENAME¶
When successful, this invocation emits the human-readable password the user expects to be associated with the OpenPGP material found in FILENAME
on standard output, encoded as UTF-8 text.
If any failure happens, it emits nothing on standard output.¶
If --ignore-cache
is supplied, or if FILENAME
is the special value -
, opac
MUST NOT look in its cache.¶
If an associated password is found in the cache, opac
MAY ask the user to confirm its use before emitting it.¶
If no associated password is found in the cache, opac
asks the user for the password associated with FILENAME
.¶
The optional argument MESSAGE
MUST be well-formed according to Section 6.7.
If MESSAGE
is not well-formed, opac get-password
fails with MALFORMED_MESSAGE
.¶
If the user declines to offer a password, or declines to permit use of the password from its cache, opac get-password
fails with USER_DECLINED
.¶
When the user's provided password is emitted, opac get-password
MUST NOT emit any other data to standard output before terminating successfully.
In particular, it must not pad the output with LINE FEED (U+000A) or other similar characters.¶
If the user fails to respond to a prompt after sufficient time has elapsed, opac get-password
fails with PROMPT_TIMED_OUT
.
If --prompt-timeout
is supplied, the specified timeout for the prompt is used.
If it is not supplied, the prompt timeout is taken from the configuration (see Section 4.3.2).¶
opac get-password
MUST NOT insert anything into the cache, or update the expiration date of anything in the cache.¶
opac cache-password [--on-reuse REUSE_DIRECTIVE] [--duration CACHE_DURATION] FILENAME¶
This invocation reads the supplied password on standard input, and injects it in the cache associated with the FILENAME
OpenPGP object in the filesystem.¶
Standard input MUST contain only a single UTF-8 encoded password, with no other information.
However, it MAY include trailing whitespace characters, which opac
strips before storing.¶
If opac cache-password
does cache the password in association with FILENAME
, it succeeds.¶
opac cache-password
MAY decline to cache the supplied password for any reason.
If it declines to cache, it SHOULD emit the reason for declining to stderr and fail with CACHE_DECLINED
or some other more specific non-zero return code.¶
If FILENAME
is -
in this invocation, it MUST NOT insert anything in the cache, and fails with INVALID_FILENAME
.¶
When associating the password with FILENAME
in the cache, it chooses the duration from the cache by looking at the --duration
argument and in its configuration (see Section 4.3.1).
It chooses the shorter of those two durations to be the default cache.¶
When a cached password is requested for use, opac
follows the associated REUSE_DIRECTIVE
, either from the command line or from the configuration (see Section 4.3.3).
If the REUSE_DIRECTIVE
appears in both the configuration and the command line, opac cache-password
uses the stricter of the two choices.¶
When inserting a password in the cache associated with FILENAME
, if any other password is in the cache associated with FILENAME
, the old value is removed from the cache.¶
opac cache-internal [--on-reuse REUSE_DIRECTIVE] [--duration CACHE_DURATION] FILENAME INTERNAL_LABEL¶
This invocation reads the supplied password on standard input, and injects it in the cache associated with the FILENAME
OpenPGP object in the filesystem and the supplied INTERNAL_LABEL
.¶
If the INTERNAL_LABEL
is malformed (see Section 6.5), it MUST NOT insert anything in the cache, and fails with MALFORMED_INTERNAL_LABEL
.
If FILENAME
is -
, it MUST NOT insert anything in the cache, and fails with INVALID_FILENAME
.¶
Standard input contains an arbitrary stream of octets, but it MUST be at least 1 octet long.
If the input is entirely empty, opac cache-internal
fails with MALFORMED_INTERNAL_VALUE
¶
If opac cache-internal
does cache the internal value in association with FILENAME
and INTERNAL_LABEL
, it succeeds.¶
opac cache-internal
MAY decline to cache the supplied internal value for any reason.
If it declines to cache, it SHOULD emit the reason for declining to stderr and fail with CACHE_DECLINED
or some other more specific non-zero return code.¶
--on-reuse
and --duration
apply in the same way as opac cache-password
(see Section 2.3).¶
When inserting an internal value in the cache associated with FILENAME
and INTERNAL_LABEL
, if any other password is in the cache associated with both FILENAME
and INTERNAL_LABEL
, the old value is removed from the cache.
Note that multiple internal values can be held in the cache associated with a given FILENAME
as long as they each have a distinct INTERNAL_LABEL
.¶
opac get-internal [--message MESSAGE] [--prompt-timeout PROMPT_TIMEOUT] FILENAME INTERNAL_LABEL¶
This invocation retrieves a previously cached internal value associated with the FILENAME
OpenPGP object in the filesystem and the supplied INTERNAL_LABEL
.¶
If an associated internal value is found in the cache, opac get-internal
MAY ask the user to confirm its use before emitting it.¶
If no associated internal value is found in the cache, opac get-internal
fails with NO_ASSOCIATED_VALUE
.¶
The optional argument MESSAGE
MUST be well-formed according to Section 6.7.
If MESSAGE
is not well-formed, opac get-internal
fails with MALFORMED_MESSAGE
.¶
If the user declines to permit use of the internal value from its cache, opac get-internal
fails with USER_DECLINED
.¶
When returning a value from the cache, opac get-internal
MUST emit exactly the same data to standard output that was provided on standard input from the corresponding call to opac cache-internal
(see Section 2.4).¶
If the user fails to respond to a prompt after sufficient time has elapsed, opac get-internal
fails with PROMPT_TIMED_OUT
.
If --prompt-timeout
is supplied, the specified timeout for the prompt is used.
If it is not supplied, the prompt timeout is taken from the configuration (see Section 4.3.2).¶
opac get-internal
MUST NOT insert anything into the cache, or update the expiration date of anything in the cache.¶
opac drop-cache [--all-subsessions|FILENAME]¶
If FILENAME
is supplied, this invocation empties the cache of all data (passwords and internal values) associated with FILENAME
.¶
If FILENAME
is not supplied, it empties the entire cache.¶
If any passwords or internal values were removed from the cache, and they were all successfully removed, the process succeeds.¶
If opac drop-cache
failed to remove any item from the cache that it was instructed to remove, it fails with COULD_NOT_REMOVE_FROM_CACHE
.
In this case, it SHOULD emit an explanation to standard error.¶
If OPAC_SUBSESSION
is set, it only removes items from the subsession-specific cache.
If OPAC_SUBSESSION
is not set, it only removes items from the main cache.¶
If --all-subsessions
is present, and either OPAC_SUBSESSION
is set of FILENAME
is also present, then opac drop-cache
fails with INCOMPATIBLE_OPTIONS
.
Otherwise, if --all-subsessions
is present, it removes all items from the main cache and from every subsession cache as well.¶
opac notify FILENAME MESSAGE¶
This invocation causes opac
to emit a non-interactive notification to the user.¶
This is intended specifically for the case where the user needs to take an action for the OpenPGP operation to succeed that the application cannot trigger on the user's behalf.
For example, if the implementation needs the user to press a button on a USB hardware token to permit the use of secret key material, it might use opac notify
so that the user knows that the application is awaiting action from them.¶
If MESSAGE
is malformed (see Section 6.7), it fails with MESSAGE_MALFORMED
, and emits nothing on standard output.¶
If it was unable to produce a notification, it fails with COULD_NOT_NOTIFY
, and emits nothing on standard output.¶
If it successfully notifies the user, it emits a NOTIFICATION_HANDLE
(see Section 6.4) on standard output.
This NOTIFICATION_HANDLE
can be used to dismiss the notification (see Section 2.8) if the application determines it is no longer needed (for example, when the button has been pressed, and the USB hardware token returned a response).¶
opac notify
ignores the cache, and uses no configuration variables, so it ignores OPAC_SUBSESSION
.¶
opac dismiss NOTIFICATION_HANDLE¶
This invocation is used solely to dismiss a notification that had been created with opac notify
(see Section 2.7).¶
The only additional argument is a NOTIFICATION_HANDLE
(see Section 6.4), which should match a value emitted from opac notify
(see Section 2.7).¶
It always succeeds, even if the NOTIFICATION_HANDLE
could not be dismissed.¶
opac dismiss
ignores the cache, and uses no configuration variables, so it ignores OPAC_SUBSESSION
.¶
opac
, like any other process, returns an error code.
The following values are defined for specific errors.¶
Value | Name | Description |
---|---|---|
0 |
SUCCESS
|
Success |
1 |
UNSUPPORTED_SUBCOMMAND
|
Unsupported subcommand |
2 |
UNSUPPORTED_OPTION
|
Unsupported option |
3 |
MALFORMED_MESSAGE
|
Message is malformed |
4 |
USER_DECLINED
|
User declined to provide a password or permit use of the cache |
5 |
PROMPT_TIMED_OUT
|
User failed to take action in response to a prompt |
6 |
CACHE_DECLINED
|
Cache entry was not added |
7 |
INVALID_FILENAME
|
An inappropriate filename was requested for caching |
8 |
COULD_NOT_REMOVE_FROM_CACHE
|
An entry remains in the cache that should have been dropped |
9 |
NO_ASSOCIATED_VALUE
|
A request was made to drop a password or internal cache entry that did not exist |
10 |
INVALID_CONFIGURATION_FILE
|
The config file was not well-formed |
11 |
MALFORMED_PASSWORD
|
Password format is not supported |
12 |
MALFORMED_INTERNAL_LABEL
|
The Internal contextual label was malformed |
13 |
MALFORMED_INTERNAL_VALUE
|
The Internal value was too short |
14 |
INCOMPATIBLE_OPTIONS
|
The command line options and environment variables supplied are mutually incompatible |
15 |
COULD_NOT_NOTIFY
|
A notification was requested, but could not be produced |
16 |
MALFORMED_SUBSESSION_ID
|
A malformed subsession identifier was supplied |
opac
should behave sensibly without any configuration file, or with an empty configuration file.¶
It always looks for its configuration as an "inifile" in exactly one location:¶
If $OPAC_SUBSESSION
is set to a non-empty string, and $OPAC_SUBSESSION_CONFIG
is set, it looks for the file in $OPAC_SUBSESSION_CONFIG
.
FIXME: if $OPAC_SUBSESSION_CONFIG
is unset, should it really fall through to reading the main session config?¶
Otherwise, if $XDG_CONFIG_HOME
is set, it looks in $XDG_CONFIG_HOME/opac/config
¶
Otherwise, it looks in ~/.config/opac/config
¶
opac
does not attempt to coalesce multiple configuration files.¶
[opac] cache_duration=10s [opac "~/.private/bob.key"] on_reuse=confirm¶
The OPAC configuration file is an "inifile".¶
It has one optional base section, simply named opac
, which contains configuration values that supersede the built-in defaults, and take effect when no override is present.¶
It has any number of file-specific subsections, which contain configuration overrides for specific objects in the filesystem.
A file-specific subsection (within opac
) is named with a representation of a path in the filesystem.
The path MUST start with either /
(indicating an absolute path to a file) or ~/
(indicating a filename relative to the user's home directory).¶
opac
MUST ignore any unexpected or unknown sections or values.
If opac
ever updates its own configuration, it MUST NOT alter any unexpected or unknown sections or values, reorder sections, or modify comments.¶
When looking up a configuration option for a prompt or a cache for a password or internal value associated with FILENAME
, opac
looks up such a key in the following places:¶
In the [opac "FILENAME"]
subsection of the config file¶
In [opac]
section of the config file¶
In the application's built-in settings¶
Each configuration option is looked up separately, so in the example above, when using ~/.private/bob.key
, on_reuse
is set to confirm
and cache_duration
is set to 10s
.¶
There are three configuration directives available.¶
The cache_duration
directive accepts a CACHE_DURATION
value (see Section 6.2).¶
A reasonable default value for cache_duration
is 1h
.¶
The prompt_timeout
directive accepts a PROMPT_TIMEOUT
value (see Section 6.1).¶
A reasonable default value for prompt_timeout
is 1m
.¶
The on_reuse
directive accepts a REUSE_DIRECTIVE
value (see Section 6.3).¶
A reasonable default value for on_reuse
is notify
.¶
OPAC is typically used directly in association with the user's session. However, it also supports the idea of a "subsession", to support an isolated cache for the use cases of test suites (see Appendix A.2) or isolated applications (see Appendix A.3).¶
A subsession shares the same prompting mechanism as the main session, but uses a completely distinct cache and configuration.¶
opac
knows to use a subsession when the environment variable OPAC_SUBSESSION
is set to a non-empty string.
The value of OPAC_SUBSESSION
is the subsession identifier.¶
The subsession is identified by an ASCII string consisting of printable (non-whitespace) characters. The subsession identifier must be at least one octet long, and no more than 64 octets.¶
If opac
is using the cache, and OPAC_SUBSESSION
is non-empty and it is not conformant to this specification, opac
fails with MALFORMED_SUBSESSION_ID
.¶
When using a subsession (that is, when OPAC_SUBSESSION
is set), if the environment variable OPAC_SUBSESSION_CONFIG
is set, opac
reads its the configuration file from the location named in OPAC_SUBSESSION_CONFIG
.¶
This represents a length of time that a prompt should be displayed to the user before it is dismissed as having been ignored.¶
It is represented in an integer number of hours, minutes, and seconds, using letter suffixes.
Hours, if present, come first and are suffixed with h
.
Minutes, if present, appear in the middle, and are suffixed with m
.
Seconds, if present, appear at the end, and are suffixed with s
.¶
If no letter suffix is present, a completely numeric value is read as seconds.¶
For example, 1m30s
means the same timeout as 90
, and 1h2s
means the same timeout as 3602
¶
Using the value 0
means that where prompting is necessary, opac
MUST NOT prompt, but should instead immediately fail with PROMPT_TIMED_OUT
.¶
Using the special value never
means to never deliberately time out the prompt.
An OPAC implementation may nevertheless fail with PROMPT_TIMED_OUT
due to other compelling circumstances.
For example, if the prompt is a confirmation prompt for the reuse of a cached value, and that cached value is removed from the cache, or if the user's session is ending and the OPAC implementation is trying to clean up, it MAY fail with PROMPT_TIMED_OUT
.¶
This represents the maximum amount of time that a value will persist in its position in the cache.¶
It is represented in an integer number of days, hours, minutes, and seconds, using letter suffixes.
Days, if present, come first and are suffixed with d
.
Hours, if present, follow days and are suffixed with h
.
Minutes, if present, follow hours, and are suffixed with m
.
Seconds, if present, appear at the end, and are suffixed with s
.¶
If no letter suffix is present, a completely numeric value is read as seconds.¶
For example, 1m30s
means the same timeout as 90
, and 1h2s
means the same timeout as 3602
¶
Using the special value never
is the same as 0
, meaning that OPAC should never cache this value.
(This is particularly useful in the configuration file; from the command line, the easier thing would be to simply never ask for caching in the first place)¶
This value may be supplied from the command line during cache insertion (see Section 2.3 and Section 2.4) and from the configuration file (see Section 4.3.1). If it is specified in both the command line and the configuration file, the shorter of the two values provided is preferred.¶
This form describes what an OPAC implementation should do when a value is elicited from the cache, and the cache can supply the value.¶
It can be one of three possible values, in increasing order of strictness: ok
, notify
, or confirm
.¶
ok
permits the use of the cached value without any interaction with the user.¶
notify
emits a standard notification while permitting the use of the cached value.¶
confirm
asks the user to confirm the use of the cached value.
In this case, the user may decline its use.¶
This value may be supplied from the command line during cache insertion (see Section 2.3 and Section 2.4) and from the configuration file (see Section 4.3.3).
If it is specified in both the command line and the configuration file, the stricter of the two values provided is preferred.
For example, if the configuration file indicates that a given value can be replayed from the cache as long as the user is notified (notify
), then a command-line option of --on-reuse=confirm
will ask the user to confirm the use, but a command-line option of --on-reuse=ok
will not suppress the notification.¶
This is a unique string that can be used to dismiss a previous notification when that notification is no longer relevant.
It is produced by opac notify
(see Section 2.7) and can be used to dismiss a specific notification with opac dismiss
(see Section 2.8).
It consists only of printable ASCII characters (no whitespace), and will be no more than 64 characters in length.¶
This is a label used by an OpenPGP implementation to associate the output of a specific cryptographic process associated with a file.
It is implementation-specific, but must be a UTF-8 encoded string of no more than 128 octets in length.
It is used, with the filename itself, as an index into the OPAC cache in opac cache-internal
(see Section 2.4) and opac get-internal
(see Section 2.5).¶
As a simple example, an OpenPGP implementation might choose to base64-encode the S2K Usage Octet and the accompanying parameters from a Secret Key Packet, and then prefix that with a short identifying label.¶
An internal value is simply a non-empty octet string of no more than 1000 octets.¶
Beyond the length limits, there are no constraints on its content.¶
This is a limited textual string that is presented to the user when the user is prompted or notified. It is a way for an application requesting data from the user to provide some additional context for what is being requested or why it is being requested.¶
It MUST be UTF-8-encoded, no more than 120 characters, with no more than two internal LINE FEED (U+000A) characters. Leading and trailing whitespace will be ignored.¶
OPAC expects a password to be a UTF-8-encoded, non-empty string.
The password MUST NOT contain any LINE FEED (U+000A) characters, and leading or trailing whitespace may be stripped.
A password MUST NOT be more than 1000 octets in length in its UTF-8 form.
If a password is supplied that cannot be brought into conformance with these specifications, opac
will fail with MALFORMED_PASSWORD
.¶
The OpenPGP standard permits the use of a password as an arbitrary bytestream, so it is possible that some OpenPGP material may be encrypted with a password that does not conform to these constraints. It is recommended to use an OpenPGP tool that does not use OPAC change to such a password to a conformant string.¶
The design of OPAC is intended to make normal, straightforward use cases easy and simple. It cannot handle all possible scenarios.¶
This section describes some details about the underlying assumptions in its design, including some descriptions of scenarios where it will not work as expected.¶
The OPAC main cache is structured as a two-level nested tree.
The top level index is by filename.
Each filename can have zero or one PASSWORD
s (see Section 6.8) associated with it.
Additionally, each filename can have zero or more INTERNAL_LABEL
s (see Section 6.5) associated with it.
If the (filename, INTERNAL_LABEL
) tuple exists, it has an INTERNAL_VALUE
associated with it.¶
Here is an example populated cache showing the indexing and the types of stored values:¶
├┬╴/tmp/secret-message.pgp │└─╴"BarPGP:23e1b0a7cbe322bf5c096cb3d1bc" → INTERNAL_VALUE ├─╴/home/bob/src/mysoft/distribution.key → PASSWORD └┬╴/home/bob/.private/bob.key → PASSWORD ├─╴"FooPGP:YXdsO2loeXFhaXdlZ2xhd2Vwa2Ewd2l0MjI" → INTERNAL_VALUE └─╴"FooPGP:Z3E0Mzl2bTtqOWZvd2x3" → INTERNAL_VALUE¶
Some observations about this example:¶
No password was cached for /tmp/secret-message.pgp
, but an internal value was cached, probably by the BarPGP implementation.¶
Only a password was cached for the distribution.key
.¶
bob.key
has cached a password and multiple internal values.
One internal value might correspond to the output of some stage of the S2K process for unlocking the primary key, while the other might be for unlocking a subkey.¶
The choice of the INTERNAL_LABEL
s present in the cache are arbitrary and up to the implementations.
In this example, FooPGP
and BarPGP
have used a namespacing approach within labels to ensure that they don't accidentally collide with each other's labels.¶
The cache depends on a view of the filesystem that aligns with the view seen by the process invoking opac
.¶
When the user's sessions starts, the OPAC cache is empty.¶
As elements are added to the cache, they should be marked clearly with expected expiration times, and removed (any associated memory wiped if possible) promptly when the stored password or internal value expires from the cache.¶
FIXME: configuration might change between insertion and retrieval from the cache; if they differ, which one should take precedence at retrieval time?¶
Each subsession, identified by a subsession ID, has an independent cache that is shaped just like the main cache.¶
If OPAC gets a cache retrieval request for a previously unknown subsession ID, it treats it as a cache miss. If OPAC gets a cache insertion request for a previously unknown subsession ID, it initializes a new, empty cache for the subsession, and inserts the password or internal value in the new cache.¶
When a cache insertion or retrieval is performed under a subsession, it works only with that subsession cache, and does not look in the main cache. When an insertion or retrieval is done outside of a subsession, it only uses the main cache, and does not look in any subsession cache.¶
See also Section 7.3 for more about subsessions.¶
Note that the opac
interface completely separates cache insertion from cache retrieval.¶
In particular, opac get-password
retrieves a password from the cache or from the user, but does not insert a user-provided password in the cache.
This is because opac
is not expected to directly interact with the OpenPGP objects, so it cannot know whether the user's supplied password works or not.¶
OPAC supports three types of user interaction, collectively known as "prompting".¶
Notification -- this might happen during cache retrieval, depending on the configuration, or explicitly via opac notify
.¶
Confirmation (boolean approval) -- this might happen during cache retrieval, depending on the configuration.¶
Elicit a string of text -- this might happen during opac get-password
, if the requested password is not found in the cache.¶
Additionally, a user of OPAC may dismiss a prior notification (when it is no longer relevant) -- but only notifications from opac notify
can be dismissed in this way.¶
Each of these forms of prompting MAY be accompanied by an application-supplied MESSAGE
, which will be clearly delimited from any OPAC-specific metadata, such as the path to the associated file, or any information about the caller of opac
.¶
OPAC needs subsessions because there are circumstances where the desired scope of an OpenPGP unlocking step isn't contiguous with the user's interactive session as a whole.¶
In particular, a software test suite is likely to want to lock and unlock various OpenPGP objects independently of the rest of the session (see Appendix A.2). And an application that spans multiple processes want to lock and unlock various OpenPGP objects without interference from the rest of the session (see Appendix A.3).¶
Note that the user's configuration values can't necessarily override subsession configuration, because subsession config might come from somewhere else, via the OPAC_SUBSESSION_CONFIG
environment variable.¶
The two main use cases for subsessions are test suites and isolated apps. A test suite wants to use its own dedicated configuration, and for reproducibility it shouldn't be influenced by configuration that happens to live outside its own workspace, even if run within the user's session.¶
Similarly, an isolated app might want the user to be able to configure how the app handles OpenPGP prompting and caching, but without asking the user to manage some system-wide configuration.¶
OPAC handles both passwords and internal values. Note the differences between how it handles each of these things:¶
opac
offers a minimal interface that can be used for a variety of common use cases.
This section provides a few simple motivational examples of how it can be integrated to support these cases.¶
These examples are not necessarily complete, but should give a flavor of the supported scenarios.¶
It should be easy to integrate OPAC into SOP, the Stateless OpenPGP command-line interface described in [I-D.dkg-openpgp-stateless-cli]. The integration can make use of prompting on its own, or it may also make use of the caching capabilities for a more usable, session-oriented approach.¶
Using POSIX-compliant shell, an OpenPGP application can combine opac
and sop
to decrypt a message with a locked Secret Key like this:¶
BOBPW=$(opac get-password bob.key) \ sop decrypt --with-key-password @ENV:BOBPW bob.key < msg.txt¶
Alternately, using slightly fancier shells like bash
or zsh
that offer command substitution:¶
sop decrypt --with-key-password <(opac get-password bob.key) \ bob.key < msg.txt¶
Other similar combinations (such as file descriptor redirection) should be straightforward from a wide range of programming environments.¶
If the OpenPGP application also wants to make use of opac
's cache, it can do something like the following POSIX shell:¶
# from SOP's standard return codes: KEY_IS_PROTECTED=67 BOBPW=$(opac get-password bob.key) PASS=$BOBPW sop decrypt --with-key-password @ENV:PASS \ bob.key < msg.txt > msg.decrypted; SOP_RESULT=$? if [ 0 -eq $SOP_RESULT ]; then printf '%s' "$PASS" | opac cache-password bob.key elif [ $KEY_IS_PROTECTED -eq $SOP_RESULT ]; then opac drop-cache bob.key fi¶
This process shows the use of the password cache: when a secret is successfully unlocked, the password is retained in the cache; if it does not work, the cache for that object is deliberately cleared so that the user would be prompted in the future.¶
A test suite that includes locked OpenPGP material typically wants to run code that would normally interact directly with the user, but should not require actual user interaction. Furthermore, a test suite typically does not want to interact with the caching or prompting of the interactive session of the developer running the test suite.¶
OPAC aims to support this use case with subsessions (see Section 5).
An OPAC subsession is identified with a unique string, and a subsession indicated to opac
through an environment variable.¶
Here is a comprehensive example in POSIX shell, wrapping a test suite so that it uses an OPAC subsession with a randomly-derived subsession identifier. It uses a dedicated configuration file to discourage prompting, and to cache everything for the entire session. It pre-emptively seeds the password cache with the password for the locked key. After the test suite is run, it cleans up after itself.¶
export OPAC_SUBSESSION=$(head -c12 < /dev/urandom | base64) cat > opac.config <<EOF [opac] cache_duration=session prompt_timeout=0 EOF export OPAC_SUBSESSION_CONFIG=$(pwd)/opac.config printf correct horse battery staple | opac cache-password ./bob.key # ... run test suite that uses the locked bob.key... opac drop-cache unset -v OPAC_SUBSESSION OPAC_SUBSESSION_CONFIG¶
An OPAC subsession can also be used by a single application, to isolate its prompting and caching state from other applications also running in the session.¶
For example, an application might be split across multiple processes, and each process should be able to use the same shared cache as the other processes in the application. But the application might not want its own OpenPGP material to be available for other applications running in the same user session.¶
An application in this situation might set up an OPAC subsession upon application start, exporting it to all child processes. The two examples in this subsection are in Python-like pseudocode.¶
def app_init(): ... if use_subsession: opac_subsession = base64(get_random(12)) subprocess_environment["OPAC_SUBSESSION"] = opac_subsession ...¶
And on application shutdown, it could flush the cache:¶
def app_cleanup(): ... if "OPAC_SUBSESSION" in subprocess_environemnt: subprocess("opac", "drop-cache") ...¶
An application that uses OpenPGP in a more fine-grained way than just interacting with a SOP instance might also want to cache some intermediate/internal values to free up resources in future processing.¶
For example, if an S2K operation takes a gigabyte of RAM and 3 seconds on the current processor, the application might want to cache the result of the S2K operation, rather than just caching the password.¶
The application can use OPAC for this kind of caching as well, but it uses a different interface than the password interface, because the user is never prompted to supply the value itself. Depending on the configuration, the user may be prompted to confirm the use of the value, though.¶
In Pythonic pseudocode, that might look something like:¶
def unlock_key(key, explanation): label = get_internal_label(key) value = subprocess("opac", "get-internal", "--message", explanation, key.filename, label) if value is None: password = subprocess("opac", "get-password", "--message", explanation, key.filename).get_stdout() value = OpenPGP_string_to_key(key.s2kparams, password) if unlock_key_with_intermediate(key, value): subprocess("opac", "cache-internal", key.filename, label).write_stdin(value) else: subprocess("opac", "drop-cache", key.filename)¶
In some cases, an OpenPGP-using application might need access to external secret key material, like a smartcard, USB hardware token, or some other device (see [I-D.dkg-openpgp-external-secrets]). In such a case, the application may need to notify the user that they need to take some additional action, like pressing a button on a USB device, or scanning their fingerprint.¶
When the application is aware of this need, it might want to send the user a generic notification. Once the user presses the button, the application might want to dismiss the notification. In Pythonic pseudocode, that might look like:¶
def handle_external_key(): handle = subprocess("opac", "notify", "--message", "Please press the USB button") ... # wait on external mechadimfor operation to complete subprocess("opac", "notify", "--clear", handle)¶
This document describes a command-line interface, as that is a widely interoperable interface that anyone can implement.¶
It might be useful to describe more nuanced, alternate interfaces that would be easier to work with for some implementations, like a C library API, or even a D-Bus interface for those platforms that use D-Bus.¶
One of the tricks for this is that the interface -- in particular opac get-password
or opac-get-internal-value
-- can be quite high-latency, particularly when awaiting a response from the user.
This probably means that a more nuanced interfce will require some kind of asynchronous interface functionality to avoid blocking the main application.¶
Much appreciation to the people who gave feedback on this document, including Jameson Rollins and Heiko Schäfer.¶
Initial sketch of the interface.¶