Crates.io | age-plugin |
lib.rs | age-plugin |
version | 0.6.0 |
source | src |
created_at | 2020-02-09 14:34:09.98771 |
updated_at | 2024-11-03 11:12:10.463525 |
description | [BETA] API for writing age plugins. |
homepage | |
repository | https://github.com/str4d/rage |
max_upload_size | |
id | 206699 |
size | 88,697 |
This crate provides an API for building age plugins.
The age file encryption format follows the "one well-oiled joint" design philosophy. The mechanism for extensibility (within a particular format version) is the recipient stanzas within the age header: file keys can be wrapped in any number of ways, and age clients are required to ignore stanzas that they do not understand.
The core APIs that exercise this mechanism are:
The age plugin system provides a mechanism for exposing these core APIs across process boundaries. It has two main components:
With this composable design, you can implement a recipient or identity that you might
use directly with the age
library crate, and also deploy it as a plugin binary for
use with clients like rage
.
age plugins are identified by an arbitrary case-insensitive string NAME
. This string
is used in three places:
age1name
(lowercase).AGE-PLUGIN-NAME-
(uppercase).age-plugin-name
.Users interact with age clients by providing either recipients for file encryption, or
identities for file decryption. When a plugin recipient or identity is provided, the
age client searches the PATH
for a binary with the corresponding plugin name.
Recipient stanza types are not required to be correlated to specific plugin names. When decrypting, age clients will pass all recipient stanzas to every connected plugin. Plugins MUST ignore stanzas that they do not know about.
A plugin binary may handle multiple recipient or identity types by being present in
the PATH
under multiple names. This can be implemented with symlinks or aliases to
the canonical binary.
Multiple plugin binaries can support the same recipient and identity types; the first
binary found in the PATH
will be used by age clients. Some Unix OSs support
"alternatives", which plugin binaries should leverage if they provide support for a
common recipient or identity type.
Note that the identity specified by a user doesn't need to point to a specific decryption key, or indeed contain any key material at all. It only needs to contain sufficient information for the plugin to locate the necessary key material.
A plugin MAY support decrypting files encrypted to native age recipients, by including
support for the x25519
recipient stanza. Such plugins will pick their own name, and
users will use identity files containing identities that specify that plugin name.
The following example uses clap
to parse CLI arguments, but any argument parsing
logic will work as long as it can detect the --age-plugin=STATE_MACHINE
flag.
use age_core::format::{FileKey, Stanza};
use age_plugin::{
identity::{self, IdentityPluginV1},
print_new_identity,
recipient::{self, RecipientPluginV1},
Callbacks, run_state_machine,
};
use clap::Parser;
use std::collections::HashMap;
use std::io;
struct RecipientPlugin;
impl RecipientPluginV1 for RecipientPlugin {
fn add_recipient(
&mut self,
index: usize,
plugin_name: &str,
bytes: &[u8],
) -> Result<(), recipient::Error> {
todo!()
}
fn add_identity(
&mut self,
index: usize,
plugin_name: &str,
bytes: &[u8]
) -> Result<(), recipient::Error> {
todo!()
}
fn wrap_file_keys(
&mut self,
file_keys: Vec<FileKey>,
mut callbacks: impl Callbacks<recipient::Error>,
) -> io::Result<Result<Vec<Vec<Stanza>>, Vec<recipient::Error>>> {
todo!()
}
}
struct IdentityPlugin;
impl IdentityPluginV1 for IdentityPlugin {
fn add_identity(
&mut self,
index: usize,
plugin_name: &str,
bytes: &[u8]
) -> Result<(), identity::Error> {
todo!()
}
fn unwrap_file_keys(
&mut self,
files: Vec<Vec<Stanza>>,
mut callbacks: impl Callbacks<identity::Error>,
) -> io::Result<HashMap<usize, Result<FileKey, Vec<identity::Error>>>> {
todo!()
}
}
#[derive(Debug, Parser)]
struct PluginOptions {
#[arg(help = "run the given age plugin state machine", long)]
age_plugin: Option<String>,
}
fn main() -> io::Result<()> {
let opts = PluginOptions::parse();
if let Some(state_machine) = opts.age_plugin {
// The plugin was started by an age client; run the state machine.
run_state_machine(
&state_machine,
Some(|| RecipientPlugin),
Some(|| IdentityPlugin),
)?;
return Ok(());
}
// Here you can assume the binary is being run directly by a user,
// and perform administrative tasks like generating keys.
Ok(())
}
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.