Skip to content

Commit a8f6a16

Browse files
committed
Migrate to yubikey 0.8
1 parent 36290c7 commit a8f6a16

File tree

8 files changed

+251
-284
lines changed

8 files changed

+251
-284
lines changed

Cargo.lock

Lines changed: 77 additions & 200 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ age-core = "0.10"
2626
age-plugin = "0.5"
2727
base64 = "0.21"
2828
bech32 = "0.9"
29+
chrono = "0.4"
2930
console = { version = "0.15", default-features = false }
3031
dialoguer = { version = "0.11", default-features = false, features = ["password"] }
3132
env_logger = "0.10"
3233
gumdrop = "0.8"
3334
hex = "0.4"
3435
log = "0.4"
35-
p256 = { version = "0.13", features = ["ecdh"] }
36+
p256 = { version = "0.13", features = ["ecdh", "pkcs8"] }
3637
pcsc = "2.4"
3738
rand = "0.8"
3839
sha2 = "0.10"
3940
which = "5"
40-
x509 = "0.2"
41-
x509-parser = "0.14"
42-
yubikey = { version = "=0.8.0-pre.0", features = ["untested"] }
41+
x509-cert = "0.2"
42+
yubikey = { version = "0.8", features = ["untested"] }
4343

4444
# Translations
4545
i18n-embed = { version = "0.15", features = ["desktop-requester", "fluent-system"] }

i18n/en-US/age_plugin_yubikey.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ plugin-err-pin-required = A PIN is required for {-yubikey} with serial {$yub
187187
188188
## Errors
189189

190+
err-build = Failed to build identity: {$err}
191+
190192
err-mgmt-key-auth = Failed to authenticate with the PIN-protected management key.
191193
rec-mgmt-key-auth =
192194
Check whether your management key is using the TDES algorithm.

src/builder.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::time::SystemTime;
2+
13
use dialoguer::Password;
24
use rand::{rngs::OsRng, RngCore};
3-
use x509::RelativeDistinguishedName;
5+
use x509_cert::{der::referenced::OwnedToRef, serial_number::SerialNumber, time::Validity};
46
use yubikey::{
57
certificate::Certificate,
68
piv::{generate as yubikey_generate, AlgorithmId, RetiredSlotId, SlotId},
@@ -12,7 +14,7 @@ use crate::{
1214
fl,
1315
key::{self, Stub},
1416
p256::Recipient,
15-
util::{Metadata, POLICY_EXTENSION_OID},
17+
util::{Metadata, UsagePolicies},
1618
BINARY_NAME, USABLE_SLOTS,
1719
};
1820

@@ -85,8 +87,10 @@ impl IdentityBuilder {
8587
}
8688
};
8789

88-
let pin_policy = self.pin_policy.unwrap_or(DEFAULT_PIN_POLICY);
89-
let touch_policy = self.touch_policy.unwrap_or(DEFAULT_TOUCH_POLICY);
90+
let policies = UsagePolicies {
91+
pin: self.pin_policy.unwrap_or(DEFAULT_PIN_POLICY),
92+
touch: self.touch_policy.unwrap_or(DEFAULT_TOUCH_POLICY),
93+
};
9094

9195
eprintln!("{}", fl!("builder-gen-key"));
9296

@@ -100,25 +104,34 @@ impl IdentityBuilder {
100104
yubikey,
101105
SlotId::Retired(slot),
102106
AlgorithmId::EccP256,
103-
pin_policy,
104-
touch_policy,
107+
policies.pin,
108+
policies.touch,
105109
)?;
106110

107-
let recipient = Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey");
111+
// TODO: https://github.com/RustCrypto/formats/issues/1488
112+
// Document `OwnedToRef` usage in top-level docs somewhere (either of the
113+
// crate, or of `SubjectPublicKeyInfoOwned` so we know how to get a reference).
114+
let recipient = Recipient::from_spki(generated.owned_to_ref())
115+
.expect("YubiKey generates a valid pubkey");
108116
let stub = Stub::new(yubikey.serial(), slot, &recipient);
109117

110118
eprintln!();
111119
eprintln!("{}", fl!("builder-gen-cert"));
112120

113121
// Pick a random serial for the new self-signed certificate.
114-
let mut serial = [0; 20];
115-
OsRng.fill_bytes(&mut serial);
122+
let serial = {
123+
// TODO: https://github.com/RustCrypto/formats/pull/1270
124+
// adds `SerialNumber::generate`; use it when available.
125+
let mut serial = [0; 20];
126+
OsRng.fill_bytes(&mut serial);
127+
SerialNumber::new(&serial).expect("valid")
128+
};
116129

117130
let name = self
118131
.name
119132
.unwrap_or(format!("age identity {}", hex::encode(stub.tag)));
120133

121-
if let PinPolicy::Always = pin_policy {
134+
if let PinPolicy::Always = policies.pin {
122135
// We need to enter the PIN again.
123136
let pin = Password::new()
124137
.with_prompt(fl!(
@@ -129,27 +142,34 @@ impl IdentityBuilder {
129142
.interact()?;
130143
yubikey.verify_pin(pin.as_bytes())?;
131144
}
132-
if let TouchPolicy::Never = touch_policy {
145+
if let TouchPolicy::Never = policies.touch {
133146
// No need to touch YubiKey
134147
} else {
135148
eprintln!("{}", fl!("builder-touch-yk"));
136149
}
137150

138-
let cert = Certificate::generate_self_signed(
151+
// TODO: https://github.com/iqlusioninc/yubikey.rs/issues/581
152+
let cert = Certificate::generate_self_signed::<_, p256::NistP256>(
139153
yubikey,
140154
SlotId::Retired(slot),
141155
serial,
142-
None,
143-
&[
144-
RelativeDistinguishedName::organization(BINARY_NAME),
145-
RelativeDistinguishedName::organizational_unit(env!("CARGO_PKG_VERSION")),
146-
RelativeDistinguishedName::common_name(&name),
147-
],
156+
Validity {
157+
not_before: SystemTime::now().try_into().map_err(Error::Build)?,
158+
not_after: x509_cert::time::Time::INFINITY,
159+
},
160+
// TODO: https://github.com/RustCrypto/formats/issues/1489
161+
format!("O={BINARY_NAME},OU={},CN={name}", env!("CARGO_PKG_VERSION"))
162+
.parse()
163+
.map_err(Error::Build)?,
148164
generated,
149-
&[x509::Extension::regular(
150-
POLICY_EXTENSION_OID,
151-
&[pin_policy.into(), touch_policy.into()],
152-
)],
165+
// TODO: https://github.com/RustCrypto/formats/issues/1490
166+
// TODO: https://github.com/iqlusioninc/yubikey.rs/issues/580
167+
|builder| {
168+
builder.add_extension(&policies).map_err(|e| match e {
169+
x509_cert::builder::Error::Asn1(error) => error,
170+
e => panic!("Cannot handle this error with the yubikey 0.8 crate: {e}"),
171+
})
172+
},
153173
)?;
154174

155175
let metadata = Metadata::extract(yubikey, slot, &cert, false).unwrap();

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::fmt;
22
use std::io;
3+
4+
use x509_cert::der;
35
use yubikey::{piv::RetiredSlotId, Serial};
46

57
use crate::util::slot_to_ui;
@@ -14,6 +16,7 @@ macro_rules! wlnfl {
1416
}
1517

1618
pub enum Error {
19+
Build(der::Error),
1720
CustomManagementKey,
1821
Dialog(dialoguer::Error),
1922
InvalidFlagCommand(String, String),
@@ -63,6 +66,7 @@ impl fmt::Debug for Error {
6366
const CHANGE_MGMT_KEY_URL: &str = "https://developers.yubico.com/yubikey-manager/";
6467

6568
match self {
69+
Error::Build(e) => wlnfl!(f, "err-build", err = e.to_string())?,
6670
Error::CustomManagementKey => {
6771
wlnfl!(f, "err-custom-mgmt-key")?;
6872
wlnfl!(

src/main.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -399,15 +399,12 @@ fn main() -> Result<(), Error> {
399399
.map(|(key, _, recipient)| {
400400
recipient.as_ref().map(|_| {
401401
// Cache the details we need to display to the user.
402-
let (_, cert) =
403-
x509_parser::parse_x509_certificate(key.certificate().as_ref())
404-
.unwrap();
405-
let (name, _) = util::extract_name(&cert, true).unwrap();
406-
let created = cert
407-
.validity()
408-
.not_before
409-
.to_rfc2822()
410-
.unwrap_or_else(|e| format!("Invalid date: {e}"));
402+
let cert = &key.certificate().cert;
403+
let (name, _) = util::extract_name(cert, true).unwrap();
404+
let created = chrono::DateTime::<chrono::Utc>::from(
405+
cert.tbs_certificate.validity.not_before.to_system_time(),
406+
)
407+
.to_rfc2822();
411408

412409
format!("{name}, created: {created}")
413410
})

src/p256.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use bech32::{ToBase32, Variant};
2-
use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
2+
use p256::{
3+
elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint},
4+
pkcs8::SubjectPublicKeyInfoRef,
5+
};
36
use sha2::{Digest, Sha256};
4-
use yubikey::{certificate::PublicKeyInfo, Certificate};
7+
use yubikey::Certificate;
58

69
use std::fmt;
710

@@ -48,11 +51,9 @@ impl Recipient {
4851
Self::from_spki(cert.subject_pki())
4952
}
5053

51-
pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option<Self> {
52-
match spki {
53-
PublicKeyInfo::EcP256(pubkey) => Self::from_encoded(pubkey),
54-
_ => None,
55-
}
54+
pub(crate) fn from_spki(spki: SubjectPublicKeyInfoRef<'_>) -> Option<Self> {
55+
// TODO: https://github.com/RustCrypto/formats/issues/1604
56+
p256::PublicKey::try_from(spki).ok().map(Recipient)
5657
}
5758

5859
/// Attempts to parse a valid YubiKey recipient from its SEC-1 encoding.

0 commit comments

Comments
 (0)