chore: update x-wing to 0.1.0-pre.2 and refactor code

This commit is contained in:
2025-11-14 12:48:44 +01:00
parent a8eaf3be50
commit dbe4901906
4 changed files with 316 additions and 302 deletions

520
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,5 @@ age-core = "0.11.0"
age-plugin = "0.6.0" age-plugin = "0.6.0"
base64 = "0.22.1" base64 = "0.22.1"
clap = { version = "4.5.1", features = ["derive"] } clap = { version = "4.5.1", features = ["derive"] }
x-wing = { version = "0.0.1-pre.1", features = ["getrandom", "zeroize"] } x-wing = { version = "0.1.0-pre.2", features = ["os_rng", "zeroize"] }
rand_core = "0.6.4" rand_core = "0.9.3"
kem = "0.3.0-pre.0"

View File

@@ -41,6 +41,6 @@ echo 'It works!' | age -e -r age1xwing1jfdy5fhryzmfg6y89dka6xr2wup9favgprpq0x542
Decryption: Decryption:
```sh ```sh
$ age -d -i x_wing_key.txt secret.enc $ age -d -i age_x_wing.key secret.enc
It works! It works!
``` ```

View File

@@ -13,7 +13,6 @@ use age_plugin::{
}; };
use base64::prelude::*; use base64::prelude::*;
use clap::Parser; use clap::Parser;
use kem::{Decapsulate, Encapsulate};
use rand_core::OsRng; use rand_core::OsRng;
use std::{ use std::{
array::TryFromSliceError, array::TryFromSliceError,
@@ -21,8 +20,8 @@ use std::{
io, io,
}; };
use x_wing::{ use x_wing::{
Ciphertext, DecapsulationKey, EncapsulationKey, CIPHERTEXT_SIZE, DECAPSULATION_KEY_SIZE, Ciphertext, Decapsulate, DecapsulationKey, Encapsulate, EncapsulationKey, CIPHERTEXT_SIZE,
ENCAPSULATION_KEY_SIZE, DECAPSULATION_KEY_SIZE, ENCAPSULATION_KEY_SIZE,
}; };
const PLUGIN_NAME: &str = "xwing"; const PLUGIN_NAME: &str = "xwing";
@@ -52,11 +51,13 @@ impl RecipientPlugin {
self.recipients self.recipients
.iter() .iter()
.map(|recipient| { .map(|recipient| {
let (ct, ss) = recipient.encapsulate(&mut OsRng).unwrap(); let (ct, ss) = recipient
.encapsulate(&mut OsRng)
.expect("X-Wing encapsulation should not fail with a valid RNG");
let wrapped_key = aead_encrypt(&ss, file_key.expose_secret()); let wrapped_key = aead_encrypt(&ss, file_key.expose_secret());
Stanza { Stanza {
tag: PLUGIN_NAME.to_string(), tag: PLUGIN_NAME.to_string(),
args: vec![BASE64_STANDARD_NO_PAD.encode(ct.as_bytes())], args: vec![BASE64_STANDARD_NO_PAD.encode(ct.to_bytes())],
body: wrapped_key, body: wrapped_key,
} }
}) })
@@ -74,20 +75,18 @@ impl RecipientPluginV1 for RecipientPlugin {
if plugin_name != PLUGIN_NAME { if plugin_name != PLUGIN_NAME {
return Err(recipient::Error::Recipient { return Err(recipient::Error::Recipient {
index, index,
message: "This recipient should not be handeled by this plugin".to_string(), message: "This recipient should not be handled by this plugin".to_string(),
}); });
} }
let pk: Result<&[u8; ENCAPSULATION_KEY_SIZE], TryFromSliceError> = bytes.try_into(); let pk: Result<&[u8; ENCAPSULATION_KEY_SIZE], TryFromSliceError> = bytes.try_into();
let pk = match pk { let pk = match pk {
Ok(x) => EncapsulationKey::from(x), Ok(x) => Ok(EncapsulationKey::from(x)),
Err(_) => { Err(_) => Err(recipient::Error::Recipient {
return Err(recipient::Error::Recipient { index,
index, message: "Invalid recipient".to_string(),
message: "Invalid recipient".to_string(), }),
}) }?;
}
};
self.recipients.push(pk); self.recipients.push(pk);
@@ -103,20 +102,16 @@ impl RecipientPluginV1 for RecipientPlugin {
if plugin_name != PLUGIN_NAME { if plugin_name != PLUGIN_NAME {
return Err(recipient::Error::Identity { return Err(recipient::Error::Identity {
index, index,
message: "This Identity should not be handeled by this plugin".to_owned(), message: "This Identity should not be handled by this plugin".to_owned(),
}); });
} }
let sk: Result<&[u8; DECAPSULATION_KEY_SIZE], TryFromSliceError> = bytes.try_into(); let sk: [u8; DECAPSULATION_KEY_SIZE] =
let sk = match sk { bytes.try_into().map_err(|_| recipient::Error::Identity {
Ok(x) => DecapsulationKey::from(x.to_owned()), index,
Err(_) => { message: "Invalid identity".to_string(),
return Err(recipient::Error::Identity { })?;
index, let sk = DecapsulationKey::from(sk);
message: "Invalid identity".to_string(),
})
}
};
self.recipients.push(sk.encapsulation_key()); self.recipients.push(sk.encapsulation_key());
@@ -145,10 +140,10 @@ struct IdentityPlugin {
} }
impl IdentityPlugin { impl IdentityPlugin {
fn decrypt_stanzas( fn decrypt_stanzas<I: Iterator<Item = (usize, Stanza)>>(
&self, &self,
file_index: usize, file_index: usize,
stanzas: Vec<(usize, Stanza)>, stanzas: I,
) -> Result<FileKey, Vec<identity::Error>> { ) -> Result<FileKey, Vec<identity::Error>> {
let mut file_key = None; let mut file_key = None;
let mut errors = Vec::new(); let mut errors = Vec::new();
@@ -216,12 +211,19 @@ impl IdentityPlugin {
}; };
let unwrapped_file_key = match aead_decrypt(&ss, FILE_KEY_BYTES, &stanza.body) { let unwrapped_file_key = match aead_decrypt(&ss, FILE_KEY_BYTES, &stanza.body) {
Ok(file_key) => FileKey::new(Box::new(file_key.try_into().unwrap_or_else(|_| { Ok(file_key) => match file_key.try_into() {
panic!( Ok(key_bytes) => FileKey::new(Box::new(key_bytes)),
"aead_decrypt returned a plaintext with a different size as {}", Err(_) => {
FILE_KEY_BYTES errors.push(identity::Error::Stanza {
) file_index,
}))), stanza_index,
message: format!(
"aead_decrypt returned a plaintext with a different size than {FILE_KEY_BYTES}"
),
});
continue;
}
},
Err(e) => { Err(e) => {
errors.push(identity::Error::Stanza { errors.push(identity::Error::Stanza {
file_index, file_index,
@@ -246,7 +248,7 @@ impl IdentityPlugin {
Err(vec![identity::Error::Stanza { Err(vec![identity::Error::Stanza {
file_index, file_index,
stanza_index: 0, stanza_index: 0,
message: "No stanzas found to be handeled by this plugin".to_string(), message: "No stanzas found to be handled by this plugin".to_string(),
}]) }])
} }
} }
@@ -261,19 +263,15 @@ impl IdentityPluginV1 for IdentityPlugin {
if plugin_name != PLUGIN_NAME { if plugin_name != PLUGIN_NAME {
return Err(identity::Error::Identity { return Err(identity::Error::Identity {
index, index,
message: "This Identity should not be handeled by this plugin".to_string(), message: "This Identity should not be handled by this plugin".to_string(),
}); });
} }
let bytes: [u8; DECAPSULATION_KEY_SIZE] = match bytes.try_into() { let bytes: [u8; DECAPSULATION_KEY_SIZE] =
Ok(x) => x, bytes.try_into().map_err(|_| identity::Error::Identity {
Err(_) => { index,
return Err(identity::Error::Identity { message: "Invalid identity".to_string(),
index, })?;
message: "Invalid identity".to_string(),
})
}
};
self.identities.push(DecapsulationKey::from(bytes)); self.identities.push(DecapsulationKey::from(bytes));
@@ -291,8 +289,7 @@ impl IdentityPluginV1 for IdentityPlugin {
let x_wing_stanzas = stanzas let x_wing_stanzas = stanzas
.into_iter() .into_iter()
.enumerate() .enumerate()
.filter(|(_, stanza)| stanza.tag == PLUGIN_NAME) .filter(|(_, stanza)| stanza.tag == PLUGIN_NAME);
.collect();
ret.insert(file_index, self.decrypt_stanzas(file_index, x_wing_stanzas)); ret.insert(file_index, self.decrypt_stanzas(file_index, x_wing_stanzas));
} }
@@ -318,7 +315,7 @@ fn main() -> io::Result<()> {
// Here you can assume the binary is being run directly by a user, and perform administrative tasks like generating keys. // Here you can assume the binary is being run directly by a user, and perform administrative tasks like generating keys.
let (sk, pk) = x_wing::generate_key_pair_from_os_rng(); let (sk, pk) = x_wing::generate_key_pair_from_os_rng();
print_new_identity(PLUGIN_NAME, sk.as_bytes(), &pk.as_bytes()); print_new_identity(PLUGIN_NAME, sk.as_bytes(), &pk.to_bytes());
Ok(()) Ok(())
} }