Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@ to 0.3.0 are beta releases.

## [Unreleased]

### Added
- Support for the native non-hybrid tagged recipient type (`age1tag1..`).
- Encryption requires making the `age-plugin-yubikey` binary available on the
`PATH` as `age-plugin-tag`, or upgrading to a client version that builds in
support for this new native recipient type.

### Changed
- MSRV is now 1.70.0.
- Encryption to an identity now uses the preferred recipient type supported for
that identity.
- `age-plugin-yubikey` now prints `age1tag1..` recipients in its CLI and
identity files instead of `age1yubikey1..` recipients. The latter is now only
shown in comments for identities generated with `age-plugin-yubikey 0.5.0` or
earlier.

## [0.5.0] - 2024-08-04
### Fixed
Expand Down
98 changes: 96 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dialoguer = { version = "0.11", default-features = false, features = ["password"
env_logger = "0.10"
gumdrop = "0.8"
hex = "0.4"
hkdf = "0.12"
hpke = { version = "0.12", default-features = false, features = ["alloc", "p256"] }
log = "0.4"
p256 = { version = "0.13", features = ["ecdh"] }
pcsc = "2.4"
Expand Down Expand Up @@ -58,5 +60,5 @@ test-with = "0.11"
which = "5"

[patch.crates-io]
age-core = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" }
age-plugin = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" }
age-core = { git = "https://github.com/str4d/rage.git", rev = "e08c450aa5d7b1cc5706094080c0042ddd60aaf7" }
age-plugin = { git = "https://github.com/str4d/rage.git", rev = "e08c450aa5d7b1cc5706094080c0042ddd60aaf7" }
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,17 @@ standard output:
$ age-plugin-yubikey --list
```

To encrypt files to these YubiKey recipients, ensure that `age-plugin-yubikey`
is accessible in your `PATH`, and then use the recipients with an age client as
normal (e.g. `rage -r age1yubikey1...`).
To encrypt files to these YubiKey recipients, ensure you have a recent version
of an age client, and then use the recipients with it as normal (e.g.
`rage -r age1tag1...`). If this does not work, make `age-plugin-yubikey`
accessible in your `PATH` with the name `age-plugin-tag` and try again.

The output of the `--list` command can also be used directly to encrypt files to
all recipients (e.g. `age -R filename.txt`).

To decrypt files encrypted to a YubiKey identity, pass the identity file to the
age client as normal (e.g. `rage -d -i yubikey-identity.txt`).
To decrypt files encrypted to a YubiKey identity, ensure that
`age-plugin-yubikey` is accessible in your `PATH`, and then pass the identity
file to the age client as normal (e.g. `rage -d -i yubikey-identity.txt`).

## Advanced topics

Expand Down
2 changes: 2 additions & 0 deletions i18n/en-US/age_plugin_yubikey.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ yubikey-metadata =
# Created: {$created}
# PIN policy: {$pin_policy}
# Touch policy: {$touch_policy}
yubikey-legacy-recipient =
# Legacy recipient: {$recipient}
yubikey-identity =
{$yubikey_metadata}
# Recipient: {$recipient}
Expand Down
6 changes: 3 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
error::Error,
fl,
key::{self, Stub},
piv_p256,
native::p256tag,
util::{Metadata, POLICY_EXTENSION_OID},
Recipient, BINARY_NAME, USABLE_SLOTS,
};
Expand Down Expand Up @@ -104,8 +104,8 @@ impl IdentityBuilder {
touch_policy,
)?;

let recipient = Recipient::PivP256(
piv_p256::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"),
let recipient = Recipient::P256Tag(
p256tag::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"),
);
let stub = Stub::new(yubikey.serial(), slot, &recipient);

Expand Down
19 changes: 13 additions & 6 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use yubikey::{

use crate::{
error::Error,
fl, piv_p256,
fl,
native::p256tag,
recipient::TAG_BYTES,
util::{otp_serial_prefix, Metadata},
Recipient, IDENTITY_PREFIX,
Expand Down Expand Up @@ -393,8 +394,8 @@ pub(crate) fn list_slots(
match key.slot() {
SlotId::Retired(slot) => {
// Only P-256 keys are compatible with us.
let recipient = piv_p256::Recipient::from_certificate(key.certificate())
.map(Recipient::PivP256);
let recipient =
p256tag::Recipient::from_certificate(key.certificate()).map(Recipient::P256Tag);
Some((key, slot, recipient))
}
_ => None,
Expand Down Expand Up @@ -592,9 +593,10 @@ impl Stub {
let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot))
.ok()
.and_then(|cert| {
piv_p256::Recipient::from_certificate(&cert)
.filter(|pk| pk.tag() == self.tag)
.map(|pk| (cert, Recipient::PivP256(pk)))
// Parse as the preferred recipient for each identity type.
p256tag::Recipient::from_certificate(&cert)
.filter(|pk| pk.static_tag() == self.tag)
.map(|pk| (cert, Recipient::P256Tag(pk)))
}) {
Some(pk) => pk,
None => {
Expand Down Expand Up @@ -628,10 +630,15 @@ pub(crate) struct Connection {
}

impl Connection {
/// Returns the preferred recipient for encrypting to this identity.
pub(crate) fn recipient(&self) -> &Recipient {
&self.pk
}

pub(crate) fn stub(&self) -> Stub {
Stub::new(self.yubikey.serial(), self.slot, &self.pk)
}

pub(crate) fn request_pin_if_necessary<E>(
&mut self,
callbacks: &mut dyn Callbacks<E>,
Expand Down
20 changes: 18 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use yubikey::{piv::RetiredSlotId, reader::Context, PinPolicy, Serial, TouchPolic
mod builder;
mod error;
mod key;
mod native;
mod piv_p256;
mod plugin;
mod util;
Expand Down Expand Up @@ -297,6 +298,12 @@ fn list(flags: PluginFlags, all: bool) -> Result<(), Error> {
all,
|_, recipient, metadata| {
println!("{metadata}");
if let Some(legacy_recipient) = recipient.legacy_recipient(&metadata) {
println!(
"{}",
fl!("yubikey-legacy-recipient", recipient = legacy_recipient)
);
}
println!("{recipient}");
},
)
Expand Down Expand Up @@ -402,7 +409,7 @@ fn main() -> Result<(), Error> {
let (_, cert) =
x509_parser::parse_x509_certificate(key.certificate().as_ref())
.unwrap();
let (name, _) = util::extract_name(&cert, true).unwrap();
let (name, _) = util::extract_name_and_version(&cert, true).unwrap();
let created = cert
.validity()
.not_before
Expand Down Expand Up @@ -612,14 +619,23 @@ fn main() -> Result<(), Error> {
Err(e) => return Err(e.into()),
};

let identity = if let Some(legacy_recipient) = recipient.legacy_recipient(&metadata) {
format!(
"{}\n{stub}",
fl!("yubikey-legacy-recipient", recipient = legacy_recipient),
)
} else {
stub.to_string()
};

writeln!(
file,
"{}",
fl!(
"yubikey-identity",
yubikey_metadata = metadata.to_string(),
recipient = recipient.to_string(),
identity = stub.to_string(),
identity = identity,
)
)?;
file.sync_data()?;
Expand Down
Loading
Loading