Merge pull request #224 from str4d/detect-critical-extensions
Reject identities with unrecognised critical extensions
This commit is contained in:
@@ -23,6 +23,12 @@ to 0.3.0 are beta releases.
|
|||||||
shown in comments for identities generated with `age-plugin-yubikey 0.5.0` or
|
shown in comments for identities generated with `age-plugin-yubikey 0.5.0` or
|
||||||
earlier.
|
earlier.
|
||||||
|
|
||||||
|
## [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
|
||||||
|
identity type.
|
||||||
|
|
||||||
## [0.5.0] - 2024-08-04
|
## [0.5.0] - 2024-08-04
|
||||||
### Fixed
|
### Fixed
|
||||||
- `age-plugin-yubikey` can now be compiled with Rust 1.80 and above.
|
- `age-plugin-yubikey` can now be compiled with Rust 1.80 and above.
|
||||||
|
|||||||
Generated
+1
-1
@@ -84,7 +84,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "age-plugin-yubikey"
|
name = "age-plugin-yubikey"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"age-core",
|
"age-core",
|
||||||
"age-plugin",
|
"age-plugin",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "age-plugin-yubikey"
|
name = "age-plugin-yubikey"
|
||||||
description = "YubiKey plugin for age clients"
|
description = "YubiKey plugin for age clients"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
authors = ["Jack Grigg <thestr4d@gmail.com>"]
|
authors = ["Jack Grigg <thestr4d@gmail.com>"]
|
||||||
repository = "https://github.com/str4d/age-plugin-yubikey"
|
repository = "https://github.com/str4d/age-plugin-yubikey"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
+33
-7
@@ -11,6 +11,7 @@ use std::io;
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::{Duration, Instant, SystemTime};
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
use x509_parser::der_parser::oid::Oid;
|
||||||
use yubikey::{
|
use yubikey::{
|
||||||
certificate::Certificate,
|
certificate::Certificate,
|
||||||
piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId},
|
piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId},
|
||||||
@@ -23,13 +24,16 @@ use crate::{
|
|||||||
fl,
|
fl,
|
||||||
native::p256tag,
|
native::p256tag,
|
||||||
recipient::TAG_BYTES,
|
recipient::TAG_BYTES,
|
||||||
util::{otp_serial_prefix, Metadata},
|
util::{otp_serial_prefix, Metadata, POLICY_EXTENSION_OID},
|
||||||
Recipient, IDENTITY_PREFIX,
|
Recipient, IDENTITY_PREFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ONE_SECOND: Duration = Duration::from_secs(1);
|
const ONE_SECOND: Duration = Duration::from_secs(1);
|
||||||
const FIFTEEN_SECONDS: Duration = Duration::from_secs(15);
|
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 {
|
pub(crate) fn is_connected(reader: Reader) -> bool {
|
||||||
filter_connected(&reader)
|
filter_connected(&reader)
|
||||||
}
|
}
|
||||||
@@ -384,6 +388,30 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the certificate to identify the preferred recipient type it corresponds to.
|
||||||
|
pub(crate) fn identify_recipient(cert: &Certificate) -> Option<Recipient> {
|
||||||
|
let known_oids = KNOWN_OIDS
|
||||||
|
.iter()
|
||||||
|
.map(|oid| Oid::from(oid).unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
p256tag::Recipient::from_certificate(cert).map(Recipient::P256Tag)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an iterator of keys that are occupying plugin-compatible slots, along with the
|
/// Returns an iterator of keys that are occupying plugin-compatible slots, along with the
|
||||||
/// corresponding recipient if the key is compatible with this plugin.
|
/// corresponding recipient if the key is compatible with this plugin.
|
||||||
pub(crate) fn list_slots(
|
pub(crate) fn list_slots(
|
||||||
@@ -393,9 +421,7 @@ pub(crate) fn list_slots(
|
|||||||
// We only use the retired slots.
|
// We only use the retired slots.
|
||||||
match key.slot() {
|
match key.slot() {
|
||||||
SlotId::Retired(slot) => {
|
SlotId::Retired(slot) => {
|
||||||
// Only P-256 keys are compatible with us.
|
let recipient = identify_recipient(key.certificate());
|
||||||
let recipient =
|
|
||||||
p256tag::Recipient::from_certificate(key.certificate()).map(Recipient::P256Tag);
|
|
||||||
Some((key, slot, recipient))
|
Some((key, slot, recipient))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -594,9 +620,9 @@ impl Stub {
|
|||||||
.ok()
|
.ok()
|
||||||
.and_then(|cert| {
|
.and_then(|cert| {
|
||||||
// Parse as the preferred recipient for each identity type.
|
// Parse as the preferred recipient for each identity type.
|
||||||
p256tag::Recipient::from_certificate(&cert)
|
identify_recipient(&cert)
|
||||||
.filter(|pk| pk.static_tag() == self.tag)
|
.filter(|recipient| recipient.static_tag() == self.tag)
|
||||||
.map(|pk| (cert, Recipient::P256Tag(pk)))
|
.map(|r| (cert, r))
|
||||||
}) {
|
}) {
|
||||||
Some(pk) => pk,
|
Some(pk) => pk,
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
Reference in New Issue
Block a user