rage
(post-v0.5.0 commit 9f824625195583c5cff0f48e5bba9b216e1fa3f6 or so) has a plugin protocol that describes how it finds plugins to run as subprocesses.
The same author has an experimental Yubikey(/PIV) plugin, see branch twitch.
That documentation covers rage
's identity/recipient naming and plugin discovery. It, however, does not say anything about the actual communication protocol, and the code is... not gentle on fresh eyes (lib.rs, identity.rs, recipient.rs). Here's what I've been able to reverse engineer.
- plugin runs as subprocess of
rage
, see age-plugin/README for discovery - command line flag
--age-plugin=
decides mode for plugin to run in - unclear: what to do about unrecognized mode
- commands on
stdin
, called stanzas, have the form:-> COMMAND [ARGS..] BASE64_BODY
- body ends implicitly after first base64 line that doesn't contain 64 characters (partial or empty line)
- trailing empty lines are here written as
\n
for the sake of clarity - responses have the same form
- TODO what's the convention for errors
- ignore unrecognized commands; parent sends noise to ensure this is done
- only some commands need a response (TODO?)
- not reporting unrecognized commands as errors is weird; future commands can never know if they went unrecognized, or if the plugin is slow at responding
This mode encrypts the ephemeral file key to the given recipients.
Phase 1: Accumulate data. Commands may be seen multiple times in any order(TODO?).
-> add-recipient RECIPIENT
\n
Recipients that are not necessarily handled by this plugin may be seen, and should be responded to with TODO some error.
-> wrap-file-key
FILE_KEY
Body is a short 16-byte secret, to be encoded to the recipients.
The order of each kind of commands matters, they are later referred to by their 0-based index.
Phase 1 ends with
-> done
\n
Phase 2: Produce results. Plugin writes stanzas
-> recipient-stanza FILE_KEY_INDEX ARGS
WRAPPED_KEY
Repeated for all recognized recipients (TODO?).
Recipient stanza ARGS
will be stored in plaintext in the age message, and available at decryption time (plugin mode identity).
or
-> error recipient RECIPIENT_INDEX
MESSAGE
or
-> error [ANYTHING_EXCEPT_recipient_OR_NOTHING]
MESSAGE
(TODO clearer meaning of errors, what scope are they reporting on.)
Phase 2 ends with plugin writing
-> done
\n
and exiting.
Phase 1: Accumulate data. Commands may be seen multiple times in any order(TODO?).
-> add-identity IDENTITY
\n
IDENTITY
is a Bech32 encoded string with the type AGE-PLUGIN-X-
, where X
is the name of your plugin.
-> recipient-stanza KEY_INDEX ARGS..
WRAPPED_KEY
Receives arguments and body returned from mode recipient-v1
command wrap-file-key
with recipient-stanza
.
Phase 1 ends with
-> done
\n
Phase 2: Produce results. Plugin writes stanzas
-> file-key KEY_INDEX
KEY
or
-> error identity INDEX
MESSAGE
TODO no way to indicate per-recipient, per-file error? e.g. can't get entropy.
Phase 2 can include callbacks, messages from plugin to parent and responses to those messages, see below.
Phase 2 ends with plugin writing
-> done
\n
and exiting.
-> msg
MESSAGE_TO_USER
-> request-secret
MESSAGE_TO_USER
gets a response
-> ok
RESPONSE
or
-> error
MESSAGE
-> unsupported
if you send command not expected in that phase