age_plugin_hpke/
lib.rs

1use std::io::{self};
2
3use agile::{agile_gen_keypair, AeadAlg, KemAlg};
4
5use bech32::{ToBase32, Variant};
6use internal::{Identity, Recipient};
7use rand::{rngs::StdRng, SeedableRng};
8
9use crate::internal::{IdentityPlugin, RecipientPlugin};
10
11pub mod agile;
12mod internal;
13
14// Plugin HRPs are age1[name] and AGE-PLUGIN-[NAME]-
15const PLUGIN_RECIPIENT_PREFIX: &str = "age1";
16const PLUGIN_IDENTITY_PREFIX: &str = "age-plugin-";
17
18pub fn run_state_machine(plugin_name: &str, state_machine: &str) -> io::Result<()> {
19    age_plugin::run_state_machine(
20        state_machine,
21        || RecipientPlugin::new(plugin_name),
22        || IdentityPlugin::new(plugin_name),
23    )
24}
25
26pub fn new_identity(kem: KemAlg, aead: AeadAlg, associated_data: &str) -> (Vec<u8>, Vec<u8>) {
27    let mut csprng = StdRng::from_entropy();
28    let keypair = agile_gen_keypair(kem.clone(), &mut csprng);
29    let identity = Identity::new(
30        kem.clone(),
31        aead.clone(),
32        kem.kdf_alg(),
33        keypair.private_key(),
34        associated_data.as_bytes(),
35    );
36    let recipient = Recipient::new(
37        kem.clone(),
38        aead,
39        kem.kdf_alg(),
40        keypair.public_key(),
41        associated_data.as_bytes(),
42    );
43
44    (identity.to_bytes(), recipient.to_bytes())
45}
46
47pub fn new_identity_to_string(plugin_name: &str, identity: &[u8], recipient: &[u8]) -> String {
48    format!(
49        "# created: {}
50# recipient: {}
51{}
52",
53        chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
54        recipient_to_string(plugin_name, recipient),
55        identity_to_string(plugin_name, identity),
56    )
57}
58
59pub fn identity_to_string(plugin_name: &str, identity: &[u8]) -> String {
60    bech32::encode(
61        &format!("{}{}-", PLUGIN_IDENTITY_PREFIX, plugin_name),
62        identity.to_base32(),
63        Variant::Bech32,
64    )
65    .expect("HRP is valid")
66    .to_uppercase()
67}
68
69pub fn identity_from_string(identity: &str) -> Vec<u8> {
70    use bech32::FromBase32;
71
72    let mut identity = identity.trim();
73    while identity.starts_with('#') {
74        identity = identity
75            .find('\n')
76            .map(|i| identity.split_at(i + 1))
77            .map(|(_, rest)| rest)
78            .unwrap_or("")
79            .trim();
80    }
81    let (_, identity_decoded, _) = bech32::decode(identity).unwrap();
82    Vec::from_base32(&identity_decoded).unwrap()
83}
84
85pub fn recipient_to_string(plugin_name: &str, recipient: &[u8]) -> String {
86    bech32::encode(
87        &format!("{}{}", PLUGIN_RECIPIENT_PREFIX, plugin_name),
88        recipient.to_base32(),
89        Variant::Bech32,
90    )
91    .expect("HRP is valid")
92}
93
94pub fn convert_identity_to_recipient(identity: &[u8]) -> Vec<u8> {
95    let recipient: Recipient = Identity::from_bytes(identity).into();
96    recipient.to_bytes()
97}