Refactors for reusability across supported recipients
This commit is contained in:
+5
-3
@@ -11,9 +11,9 @@ use crate::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
fl,
|
fl,
|
||||||
key::{self, Stub},
|
key::{self, Stub},
|
||||||
p256::Recipient,
|
piv_p256,
|
||||||
util::{Metadata, POLICY_EXTENSION_OID},
|
util::{Metadata, POLICY_EXTENSION_OID},
|
||||||
BINARY_NAME, USABLE_SLOTS,
|
Recipient, BINARY_NAME, USABLE_SLOTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) const DEFAULT_PIN_POLICY: PinPolicy = PinPolicy::Once;
|
pub(crate) const DEFAULT_PIN_POLICY: PinPolicy = PinPolicy::Once;
|
||||||
@@ -104,7 +104,9 @@ impl IdentityBuilder {
|
|||||||
touch_policy,
|
touch_policy,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let recipient = Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey");
|
let recipient = Recipient::PivP256(
|
||||||
|
piv_p256::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"),
|
||||||
|
);
|
||||||
let stub = Stub::new(yubikey.serial(), slot, &recipient);
|
let stub = Stub::new(yubikey.serial(), slot, &recipient);
|
||||||
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
|
|||||||
+8
-12
@@ -20,11 +20,10 @@ use yubikey::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fl,
|
fl, piv_p256,
|
||||||
p256::{Recipient, TAG_BYTES},
|
recipient::TAG_BYTES,
|
||||||
piv_p256,
|
|
||||||
util::{otp_serial_prefix, Metadata},
|
util::{otp_serial_prefix, Metadata},
|
||||||
IDENTITY_PREFIX,
|
Recipient, IDENTITY_PREFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ONE_SECOND: Duration = Duration::from_secs(1);
|
const ONE_SECOND: Duration = Duration::from_secs(1);
|
||||||
@@ -394,7 +393,8 @@ pub(crate) fn list_slots(
|
|||||||
match key.slot() {
|
match key.slot() {
|
||||||
SlotId::Retired(slot) => {
|
SlotId::Retired(slot) => {
|
||||||
// Only P-256 keys are compatible with us.
|
// Only P-256 keys are compatible with us.
|
||||||
let recipient = Recipient::from_certificate(key.certificate());
|
let recipient = piv_p256::Recipient::from_certificate(key.certificate())
|
||||||
|
.map(Recipient::PivP256);
|
||||||
Some((key, slot, recipient))
|
Some((key, slot, recipient))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -449,7 +449,7 @@ impl Stub {
|
|||||||
Stub {
|
Stub {
|
||||||
serial,
|
serial,
|
||||||
slot,
|
slot,
|
||||||
tag: recipient.tag(),
|
tag: recipient.static_tag(),
|
||||||
identity_index: 0,
|
identity_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,10 +476,6 @@ impl Stub {
|
|||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn matches(&self, line: &piv_p256::RecipientLine) -> bool {
|
|
||||||
self.tag == line.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns:
|
/// Returns:
|
||||||
/// - `Ok(Ok(Some(connection)))` if we successfully connected to this YubiKey.
|
/// - `Ok(Ok(Some(connection)))` if we successfully connected to this YubiKey.
|
||||||
/// - `Ok(Ok(None))` if the user told us to skip this YubiKey.
|
/// - `Ok(Ok(None))` if the user told us to skip this YubiKey.
|
||||||
@@ -601,9 +597,9 @@ impl Stub {
|
|||||||
let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot))
|
let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot))
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|cert| {
|
.and_then(|cert| {
|
||||||
Recipient::from_certificate(&cert)
|
piv_p256::Recipient::from_certificate(&cert)
|
||||||
.filter(|pk| pk.tag() == self.tag)
|
.filter(|pk| pk.tag() == self.tag)
|
||||||
.map(|pk| (cert, pk))
|
.map(|pk| (cert, Recipient::PivP256(pk)))
|
||||||
}) {
|
}) {
|
||||||
Some(pk) => pk,
|
Some(pk) => pk,
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
+6
-5
@@ -17,16 +17,17 @@ use yubikey::{piv::RetiredSlotId, reader::Context, PinPolicy, Serial, TouchPolic
|
|||||||
mod builder;
|
mod builder;
|
||||||
mod error;
|
mod error;
|
||||||
mod key;
|
mod key;
|
||||||
mod p256;
|
|
||||||
mod piv_p256;
|
mod piv_p256;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
mod recipient;
|
||||||
|
use recipient::Recipient;
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
const PLUGIN_NAME: &str = "yubikey";
|
const PLUGIN_NAME: &str = "yubikey";
|
||||||
const BINARY_NAME: &str = "age-plugin-yubikey";
|
const BINARY_NAME: &str = "age-plugin-yubikey";
|
||||||
const RECIPIENT_PREFIX: &str = "age1yubikey";
|
|
||||||
const IDENTITY_PREFIX: &str = "age-plugin-yubikey-";
|
const IDENTITY_PREFIX: &str = "age-plugin-yubikey-";
|
||||||
|
|
||||||
const USABLE_SLOTS: [RetiredSlotId; 20] = [
|
const USABLE_SLOTS: [RetiredSlotId; 20] = [
|
||||||
@@ -193,7 +194,7 @@ fn generate(flags: PluginFlags) -> Result<(), Error> {
|
|||||||
fn print_single(
|
fn print_single(
|
||||||
serial: Option<Serial>,
|
serial: Option<Serial>,
|
||||||
slot: RetiredSlotId,
|
slot: RetiredSlotId,
|
||||||
printer: impl Fn(key::Stub, p256::Recipient, util::Metadata),
|
printer: impl Fn(key::Stub, Recipient, util::Metadata),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut yubikey = key::open(serial)?;
|
let mut yubikey = key::open(serial)?;
|
||||||
|
|
||||||
@@ -215,7 +216,7 @@ fn print_multiple(
|
|||||||
kind: &str,
|
kind: &str,
|
||||||
serial: Option<Serial>,
|
serial: Option<Serial>,
|
||||||
all: bool,
|
all: bool,
|
||||||
printer: impl Fn(key::Stub, p256::Recipient, util::Metadata),
|
printer: impl Fn(key::Stub, Recipient, util::Metadata),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut readers = Context::open()?;
|
let mut readers = Context::open()?;
|
||||||
|
|
||||||
@@ -255,7 +256,7 @@ fn print_details(
|
|||||||
kind: &str,
|
kind: &str,
|
||||||
flags: PluginFlags,
|
flags: PluginFlags,
|
||||||
all: bool,
|
all: bool,
|
||||||
printer: impl Fn(key::Stub, p256::Recipient, util::Metadata),
|
printer: impl Fn(key::Stub, Recipient, util::Metadata),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if let Some(slot) = flags.slot {
|
if let Some(slot) = flags.slot {
|
||||||
print_single(flags.serial, slot, printer)
|
print_single(flags.serial, slot, printer)
|
||||||
|
|||||||
+16
-20
@@ -11,12 +11,14 @@ use p256::{
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
use crate::{key::Connection, p256::Recipient};
|
use crate::{key::Connection, recipient::TAG_BYTES, util::base64_arg};
|
||||||
|
|
||||||
|
mod recipient;
|
||||||
|
pub(crate) use recipient::Recipient;
|
||||||
|
|
||||||
const STANZA_TAG: &str = "piv-p256";
|
const STANZA_TAG: &str = "piv-p256";
|
||||||
pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256";
|
pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256";
|
||||||
|
|
||||||
const TAG_BYTES: usize = 4;
|
|
||||||
const EPK_BYTES: usize = 33;
|
const EPK_BYTES: usize = 33;
|
||||||
const ENCRYPTED_FILE_KEY_BYTES: usize = 32;
|
const ENCRYPTED_FILE_KEY_BYTES: usize = 32;
|
||||||
|
|
||||||
@@ -81,17 +83,6 @@ impl RecipientLine {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base64_arg<A: AsRef<[u8]>, B: AsMut<[u8]>>(arg: &A, mut buf: B) -> Option<B> {
|
|
||||||
if arg.as_ref().len() != ((4 * buf.as_mut().len()) + 2) / 3 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
BASE64_STANDARD_NO_PAD
|
|
||||||
.decode_slice_unchecked(arg, buf.as_mut())
|
|
||||||
.ok()
|
|
||||||
.and_then(|len| (len == buf.as_mut().len()).then_some(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tag, epk_bytes) = match &s.args[..] {
|
let (tag, epk_bytes) = match &s.args[..] {
|
||||||
[tag, epk_bytes] => (
|
[tag, epk_bytes] => (
|
||||||
base64_arg(tag, [0; TAG_BYTES]),
|
base64_arg(tag, [0; TAG_BYTES]),
|
||||||
@@ -110,15 +101,17 @@ impl RecipientLine {
|
|||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn wrap_file_key(file_key: &FileKey, pk: &Recipient) -> Self {
|
impl Recipient {
|
||||||
|
pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> RecipientLine {
|
||||||
let esk = EphemeralSecret::random(&mut OsRng);
|
let esk = EphemeralSecret::random(&mut OsRng);
|
||||||
let epk = esk.public_key();
|
let epk = esk.public_key();
|
||||||
let epk_bytes = EphemeralKeyBytes::from_public_key(&epk);
|
let epk_bytes = EphemeralKeyBytes::from_public_key(&epk);
|
||||||
|
|
||||||
let shared_secret = esk.diffie_hellman(pk.public_key());
|
let shared_secret = esk.diffie_hellman(self.public_key());
|
||||||
|
|
||||||
let salt = salt(&epk_bytes, pk);
|
let salt = salt(&epk_bytes, self);
|
||||||
|
|
||||||
let enc_key = {
|
let enc_key = {
|
||||||
let mut okm = [0; 32];
|
let mut okm = [0; 32];
|
||||||
@@ -136,21 +129,24 @@ impl RecipientLine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RecipientLine {
|
RecipientLine {
|
||||||
tag: pk.tag(),
|
tag: self.tag(),
|
||||||
epk_bytes,
|
epk_bytes,
|
||||||
encrypted_file_key,
|
encrypted_file_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecipientLine {
|
||||||
pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result<FileKey, ()> {
|
pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result<FileKey, ()> {
|
||||||
assert_eq!(self.tag, conn.recipient().tag());
|
let crate::recipient::Recipient::PivP256(recipient) = conn.recipient();
|
||||||
|
assert_eq!(self.tag, recipient.tag());
|
||||||
|
|
||||||
|
let salt = salt(&self.epk_bytes, recipient);
|
||||||
|
|
||||||
// The YubiKey API for performing scalar multiplication takes the point in its
|
// The YubiKey API for performing scalar multiplication takes the point in its
|
||||||
// uncompressed SEC-1 encoding.
|
// uncompressed SEC-1 encoding.
|
||||||
let shared_secret = conn.p256_ecdh(self.epk_bytes.decompress().as_bytes())?;
|
let shared_secret = conn.p256_ecdh(self.epk_bytes.decompress().as_bytes())?;
|
||||||
|
|
||||||
let salt = salt(&self.epk_bytes, conn.recipient());
|
|
||||||
|
|
||||||
let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref());
|
let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref());
|
||||||
|
|
||||||
// A failure to decrypt is fatal, because we assume that we won't
|
// A failure to decrypt is fatal, because we assume that we won't
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use bech32::{ToBase32, Variant};
|
use bech32::{ToBase32, Variant};
|
||||||
use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use yubikey::{certificate::PublicKeyInfo, Certificate};
|
use yubikey::{certificate::PublicKeyInfo, Certificate};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::RECIPIENT_PREFIX;
|
use crate::recipient::{static_tag, TAG_BYTES};
|
||||||
|
|
||||||
pub(crate) const TAG_BYTES: usize = 4;
|
const RECIPIENT_PREFIX: &str = "age1yubikey";
|
||||||
|
|
||||||
/// Wrapper around a compressed secp256r1 curve point.
|
/// Wrapper around a compressed secp256r1 curve point.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -69,8 +68,7 @@ impl Recipient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tag(&self) -> [u8; TAG_BYTES] {
|
pub(crate) fn tag(&self) -> [u8; TAG_BYTES] {
|
||||||
let tag = Sha256::digest(self.to_encoded().as_bytes());
|
static_tag(self.to_encoded().as_bytes())
|
||||||
(&tag[0..TAG_BYTES]).try_into().expect("length is correct")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exposes the wrapped public key.
|
/// Exposes the wrapped public key.
|
||||||
+31
-13
@@ -7,7 +7,7 @@ use age_plugin::{
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::{fl, key, p256::Recipient, piv_p256, PLUGIN_NAME};
|
use crate::{fl, key, piv_p256, Recipient, PLUGIN_NAME};
|
||||||
|
|
||||||
pub(crate) struct Handler;
|
pub(crate) struct Handler;
|
||||||
|
|
||||||
@@ -37,11 +37,7 @@ impl RecipientPluginV1 for RecipientPlugin {
|
|||||||
plugin_name: &str,
|
plugin_name: &str,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Result<(), recipient::Error> {
|
) -> Result<(), recipient::Error> {
|
||||||
if let Some(pk) = if plugin_name == PLUGIN_NAME {
|
if let Some(pk) = Recipient::from_bytes(plugin_name, bytes) {
|
||||||
Recipient::from_bytes(bytes)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
} {
|
|
||||||
self.recipients.push(pk);
|
self.recipients.push(pk);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -114,7 +110,7 @@ impl RecipientPluginV1 for RecipientPlugin {
|
|||||||
self.recipients
|
self.recipients
|
||||||
.iter()
|
.iter()
|
||||||
.chain(yk_recipients.iter())
|
.chain(yk_recipients.iter())
|
||||||
.map(|pk| piv_p256::RecipientLine::wrap_file_key(&file_key, pk).into())
|
.map(|pk| pk.wrap_file_key(&file_key))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
@@ -159,16 +155,16 @@ impl IdentityPluginV1 for IdentityPlugin {
|
|||||||
let mut file_keys = HashMap::with_capacity(files.len());
|
let mut file_keys = HashMap::with_capacity(files.len());
|
||||||
|
|
||||||
// Filter to files / stanzas for which we have matching YubiKeys
|
// Filter to files / stanzas for which we have matching YubiKeys
|
||||||
let mut candidate_stanzas: Vec<(&key::Stub, HashMap<usize, Vec<piv_p256::RecipientLine>>)> =
|
let mut candidate_stanzas: Vec<(&key::Stub, HashMap<usize, Vec<SupportedStanza>>)> = self
|
||||||
self.yubikeys
|
.yubikeys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stub| (stub, HashMap::new()))
|
.map(|stub| (stub, HashMap::new()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (file, stanzas) in files.iter().enumerate() {
|
for (file, stanzas) in files.into_iter().enumerate() {
|
||||||
for (stanza_index, stanza) in stanzas.iter().enumerate() {
|
for (stanza_index, stanza) in stanzas.into_iter().enumerate() {
|
||||||
match (
|
match (
|
||||||
piv_p256::RecipientLine::from_stanza(stanza).map(|res| {
|
SupportedStanza::parse(stanza).map(|res| {
|
||||||
res.map_err(|_| identity::Error::Stanza {
|
res.map_err(|_| identity::Error::Stanza {
|
||||||
file_index: file,
|
file_index: file,
|
||||||
stanza_index,
|
stanza_index,
|
||||||
@@ -182,7 +178,7 @@ impl IdentityPluginV1 for IdentityPlugin {
|
|||||||
// A line will match at most one YubiKey.
|
// A line will match at most one YubiKey.
|
||||||
if let Some(files) =
|
if let Some(files) =
|
||||||
candidate_stanzas.iter_mut().find_map(|(stub, files)| {
|
candidate_stanzas.iter_mut().find_map(|(stub, files)| {
|
||||||
if stub.matches(&line) {
|
if line.matches_stub(stub) {
|
||||||
Some(files)
|
Some(files)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -274,3 +270,25 @@ impl IdentityPluginV1 for IdentityPlugin {
|
|||||||
Ok(file_keys)
|
Ok(file_keys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SupportedStanza {
|
||||||
|
PivP256(piv_p256::RecipientLine),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupportedStanza {
|
||||||
|
fn parse(stanza: Stanza) -> Option<Result<Self, ()>> {
|
||||||
|
piv_p256::RecipientLine::from_stanza(&stanza).map(|res| res.map(Self::PivP256))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn matches_stub(&self, stub: &key::Stub) -> bool {
|
||||||
|
match self {
|
||||||
|
SupportedStanza::PivP256(line) => stub.tag == line.tag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unwrap_file_key(&self, conn: &mut key::Connection) -> Result<FileKey, ()> {
|
||||||
|
match self {
|
||||||
|
SupportedStanza::PivP256(line) => line.unwrap_file_key(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use age_core::format::{FileKey, Stanza};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
use crate::{piv_p256, PLUGIN_NAME};
|
||||||
|
|
||||||
|
pub(crate) const TAG_BYTES: usize = 4;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) enum Recipient {
|
||||||
|
PivP256(piv_p256::Recipient),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Recipient {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Recipient::PivP256(recipient) => recipient.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recipient {
|
||||||
|
/// Attempts to parse a supported YubiKey recipient.
|
||||||
|
pub(crate) fn from_bytes(plugin_name: &str, bytes: &[u8]) -> Option<Self> {
|
||||||
|
match plugin_name {
|
||||||
|
PLUGIN_NAME => piv_p256::Recipient::from_bytes(bytes).map(Self::PivP256),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the static tag for this recipient.
|
||||||
|
pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] {
|
||||||
|
match self {
|
||||||
|
Recipient::PivP256(recipient) => recipient.tag(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> Stanza {
|
||||||
|
match self {
|
||||||
|
Recipient::PivP256(recipient) => recipient.wrap_file_key(file_key).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn static_tag(pk: &[u8]) -> [u8; TAG_BYTES] {
|
||||||
|
Sha256::digest(pk)[0..TAG_BYTES]
|
||||||
|
.try_into()
|
||||||
|
.expect("length is correct")
|
||||||
|
}
|
||||||
+13
-1
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
|
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
|
||||||
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
|
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
|
||||||
use yubikey::{
|
use yubikey::{
|
||||||
piv::{RetiredSlotId, SlotId},
|
piv::{RetiredSlotId, SlotId},
|
||||||
@@ -8,7 +9,7 @@ use yubikey::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
use crate::{error::Error, key::Stub, p256::Recipient, BINARY_NAME, USABLE_SLOTS};
|
use crate::{error::Error, key::Stub, Recipient, BINARY_NAME, USABLE_SLOTS};
|
||||||
|
|
||||||
pub(crate) const POLICY_EXTENSION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 41482, 3, 8];
|
pub(crate) const POLICY_EXTENSION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 41482, 3, 8];
|
||||||
|
|
||||||
@@ -219,3 +220,14 @@ pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadat
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn base64_arg<A: AsRef<[u8]>, B: AsMut<[u8]>>(arg: &A, mut buf: B) -> Option<B> {
|
||||||
|
if arg.as_ref().len() != ((4 * buf.as_mut().len()) + 2) / 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE64_STANDARD_NO_PAD
|
||||||
|
.decode_slice_unchecked(arg, buf.as_mut())
|
||||||
|
.ok()
|
||||||
|
.and_then(|len| (len == buf.as_mut().len()).then_some(buf))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user