From 9503f406aee05c6da46c0e11ae5138b1ae6a0597 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Apr 2026 04:12:35 +0100 Subject: [PATCH 1/4] Reject identities with unrecognised critical extensions We don't know how to correctly use these identities. In particular, some identities store parts of their private key material in certificate extensions to work around hardware limitations. Not understanding these extensions could lead to encrypting with the wrong protocol and violating security assumptions. --- CHANGELOG.md | 6 ++++++ src/key.rs | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3301c89..d55bb38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ to 0.3.0 are beta releases. ## [Unreleased] +## [0.3.4] - PLANNED +### Fixed +- `age-plugin-yubikey` now completely ignores any identity that has unrecognised + critical extensions in its certificate, to ensure it doesn't misuse a newer + identity type. + ## [0.3.3] - 2023-02-11 ### Fixed - When `age-plugin-yubikey` assists the user in changing their PIN from the diff --git a/src/key.rs b/src/key.rs index 9aa81ed..294f185 100644 --- a/src/key.rs +++ b/src/key.rs @@ -15,6 +15,7 @@ use std::io; use std::iter; use std::thread::sleep; use std::time::{Duration, Instant, SystemTime}; +use x509_parser::der_parser::oid::Oid; use yubikey::{ certificate::Certificate, piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, @@ -27,13 +28,16 @@ use crate::{ fl, format::{RecipientLine, STANZA_KEY_LABEL}, p256::{Recipient, TAG_BYTES}, - util::{otp_serial_prefix, Metadata}, + util::{otp_serial_prefix, Metadata, POLICY_EXTENSION_OID}, IDENTITY_PREFIX, }; const ONE_SECOND: Duration = Duration::from_secs(1); const FIFTEEN_SECONDS: Duration = Duration::from_secs(15); +/// The set of OIDs that we understand and use when parsing YubiKey slot certificates. +const KNOWN_OIDS: &[&[u64]] = &[POLICY_EXTENSION_OID]; + pub(crate) fn is_connected(reader: Reader) -> bool { filter_connected(&reader) } @@ -339,6 +343,30 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { Ok(()) } +/// Parses the certificate to identify the preferred recipient type it corresponds to. +pub(crate) fn identify_recipient(cert: &Certificate) -> Option { + let known_oids = KNOWN_OIDS + .iter() + .map(|oid| Oid::from(oid).unwrap()) + .collect::>(); + + // If the certificate contains any unrecognised critical extensions, reject it: we + // don't know how to correctly use the identity. In particular, some identities store + // parts of their private key material in certificate extensions to work around + // hardware limitations. Not understanding these extensions could lead to encrypting + // with the wrong protocol and violating security assumptions. + let (_, c) = x509_parser::parse_x509_certificate(cert.as_ref()).ok()?; + if c.tbs_certificate + .extensions() + .iter() + .any(|ext| ext.critical && !known_oids.contains(&ext.oid)) + { + return None; + } + + Recipient::from_certificate(cert) +} + /// Returns an iterator of keys that are occupying plugin-compatible slots, along with the /// corresponding recipient if the key is compatible with this plugin. pub(crate) fn list_slots( @@ -348,8 +376,7 @@ pub(crate) fn list_slots( // We only use the retired slots. match key.slot() { SlotId::Retired(slot) => { - // Only P-256 keys are compatible with us. - let recipient = Recipient::from_certificate(key.certificate()); + let recipient = identify_recipient(key.certificate()); Some((key, slot, recipient)) } _ => None, @@ -556,9 +583,9 @@ impl Stub { let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() .and_then(|cert| { - Recipient::from_certificate(&cert) - .filter(|pk| pk.tag() == self.tag) - .map(|pk| (cert, pk)) + identify_recipient(&cert) + .filter(|recipient| recipient.tag() == self.tag) + .map(|r| (cert, r)) }) { Some(pk) => pk, None => { From bf081835c4d0f7d640231415ce4efee1ad4d2d73 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Apr 2026 04:14:54 +0100 Subject: [PATCH 2/4] Release 0.3.4 --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55bb38..5bae3c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ to 0.3.0 are beta releases. ## [Unreleased] -## [0.3.4] - PLANNED +## [0.3.4] - 2026-04-08 ### Fixed - `age-plugin-yubikey` now completely ignores any identity that has unrecognised critical extensions in its certificate, to ensure it doesn't misuse a newer diff --git a/Cargo.lock b/Cargo.lock index 0b15c6e..bcf9494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ dependencies = [ [[package]] name = "age-plugin-yubikey" -version = "0.3.3" +version = "0.3.4" dependencies = [ "age-core", "age-plugin", diff --git a/Cargo.toml b/Cargo.toml index db38619..d56f949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "age-plugin-yubikey" description = "YubiKey plugin for age clients" -version = "0.3.3" +version = "0.3.4" authors = ["Jack Grigg "] repository = "https://github.com/str4d/age-plugin-yubikey" readme = "README.md" From 23a1f61e5a982c8f2055e52084e298d25499d7da Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Apr 2026 04:20:54 +0100 Subject: [PATCH 3/4] v0.4.1 --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cffa7a4..e2aacee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ to 0.3.0 are beta releases. ## [Unreleased] -## [0.3.4] - 2026-04-08 +## [0.3.4], [0.4.1] - 2026-04-08 ### Fixed - `age-plugin-yubikey` now completely ignores any identity that has unrecognised critical extensions in its certificate, to ensure it doesn't misuse a newer diff --git a/Cargo.lock b/Cargo.lock index 9c4b45c..5521a3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ [[package]] name = "age-plugin-yubikey" -version = "0.4.0" +version = "0.4.1" dependencies = [ "age-core", "age-plugin", diff --git a/Cargo.toml b/Cargo.toml index 95067e5..fd8e6fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "age-plugin-yubikey" description = "YubiKey plugin for age clients" -version = "0.4.0" +version = "0.4.1" authors = ["Jack Grigg "] repository = "https://github.com/str4d/age-plugin-yubikey" readme = "README.md" From 9329e31dd3d0819d5d7b739aef59e455e10fcfa1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 8 Apr 2026 04:27:00 +0100 Subject: [PATCH 4/4] v0.5.1 --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b5294..a3049a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ to 0.3.0 are beta releases. ## [Unreleased] -## [0.3.4], [0.4.1] - 2026-04-08 +## [0.3.4], [0.4.1], [0.5.1] - 2026-04-08 ### Fixed - `age-plugin-yubikey` now completely ignores any identity that has unrecognised critical extensions in its certificate, to ensure it doesn't misuse a newer diff --git a/Cargo.lock b/Cargo.lock index 41d04cf..13464e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ [[package]] name = "age-plugin-yubikey" -version = "0.5.0" +version = "0.5.1" dependencies = [ "age-core", "age-plugin", diff --git a/Cargo.toml b/Cargo.toml index afcabec..b4661ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "age-plugin-yubikey" description = "YubiKey plugin for age clients" -version = "0.5.0" +version = "0.5.1" authors = ["Jack Grigg "] repository = "https://github.com/str4d/age-plugin-yubikey" readme = "README.md"