feat: allow enrolling new credentials

This commit is contained in:
Grimmauld 2025-02-15 14:40:05 +01:00
parent 75c869defd
commit 6a88a4f3de
No known key found for this signature in database
3 changed files with 95 additions and 8 deletions

View file

@ -8,6 +8,7 @@ pub const OATH_AID: [u8; 7] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01];
pub const DEFAULT_PERIOD: Duration = Duration::from_secs(30);
pub const DEFAULT_DIGITS: OathDigits = OathDigits::Six;
pub const DEFAULT_IMF: u32 = 0;
pub const HMAC_MINIMUM_KEY_SIZE: usize = 14;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
@ -114,7 +115,7 @@ pub enum Tag {
Touch = 0x7c,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum HashAlgo {
Sha1 = 0x01,

View file

@ -1,5 +1,8 @@
// SPDX-License-Identifier: BSD-3-Clause
use lib_ykoath2::constants::{HashAlgo, OathDigits, OathType, DEFAULT_PERIOD};
use lib_ykoath2::oath_credential::OathCredential;
use lib_ykoath2::oath_credentialid::CredentialIDData;
use lib_ykoath2::OathSession;
// use crate::args::Cli;
@ -36,6 +39,30 @@ fn main() {
// session.unlock_session(&session.derive_key("1234")).unwrap();
// session.unset_key().unwrap();
/* let cred = OathCredential {
device_id: session.name.clone(),
id_data: CredentialIDData {
name: "test_cred".to_string(),
oath_type: OathType::Totp,
issuer: None,
period: DEFAULT_PERIOD,
},
touch_required: false,
};
session
.put_credential(
cred.clone(),
"f5up4ub3dw".as_bytes(),
HashAlgo::Sha256,
OathDigits::Six,
None,
)
.unwrap();
let calculated = session.calculate_refreshable_code(&cred, None).unwrap();
println!("freshly defined oath: {}", calculated);
session.delete_code(cred).unwrap(); */
println!("YubiKey version is {:?}", session.get_version());
for c in session.list_oath_codes().unwrap() {
println!("{}", c);

View file

@ -1,10 +1,10 @@
#![allow(unused)]
mod constants;
pub mod constants;
use constants::*;
mod transaction;
use transaction::*;
mod oath_credential;
mod oath_credentialid;
pub mod oath_credential;
pub mod oath_credentialid;
/// Utilities for interacting with YubiKey OATH/TOTP functionality
use std::{
fmt::Display,
@ -77,7 +77,7 @@ impl<'a> RefreshableOathCredential<'a> {
let timestamp = SystemTime::now();
let refresh_result = self
.refresh_provider
.calculate_code(self.cred.to_owned(), Some(timestamp))
.calculate_code(&self.cred, Some(timestamp))
.ok();
self.force_update(refresh_result, timestamp);
}
@ -267,7 +267,7 @@ impl OathSession {
Ok(new)
}
pub fn delete_code(&self, cred: OathCredential) -> Result<ApduResponse, Error> {
pub fn delete_code(&self, cred: OathCredential) -> Result<(), Error> {
if self.locked {
return Err(Error::Authentication);
}
@ -278,7 +278,53 @@ impl OathSession {
0,
0,
Some(&cred.id_data.as_tlv()),
)
)?;
Ok(())
}
pub fn put_credential(
&self,
cred: OathCredential,
secret: &[u8],
algo: HashAlgo,
digits: OathDigits,
counter: Option<u32>,
) -> Result<(), Error> {
if self.locked {
return Err(Error::Authentication);
}
let cred_id = cred.id_data.format_cred_id();
let secret_short = _hmac_shorten_key(secret, algo);
let mut secret_padded = [0u8; HMAC_MINIMUM_KEY_SIZE];
let len_to_copy = secret_short.len().min(HMAC_MINIMUM_KEY_SIZE); // Avoid copying more than 14
secret_padded[(HMAC_MINIMUM_KEY_SIZE - len_to_copy)..]
.copy_from_slice(&secret_short[..len_to_copy]);
let mut data = [
cred.id_data.as_tlv(),
to_tlv(
Tag::Key,
&[
[(cred.id_data.oath_type as u8) | (algo as u8), digits as u8].to_vec(),
secret_padded.to_vec(),
]
.concat(),
),
]
.concat();
if cred.touch_required {
data.extend([Tag::Property as u8, 2u8]); // FIXME: python impl does *not* send this to tlv, which seems to work but feels wrong. See also: https://github.com/Yubico/yubikey-manager/issues/660
}
if let Some(c) = counter {
data.extend(to_tlv(Tag::Imf, &c.to_be_bytes()));
}
self.transaction_context
.apdu(0, Instruction::Put as u8, 0, 0, Some(&data))?;
Ok(())
}
pub fn derive_key(&self, passphrase: &str) -> Vec<u8> {
@ -286,9 +332,22 @@ impl OathSession {
.to_vec()
}
pub fn calculate_refreshable_code(
&self,
cred: &OathCredential,
timestamp_sys: Option<SystemTime>,
) -> Result<RefreshableOathCredential, Error> {
let timestamp = timestamp_sys.unwrap_or_else(SystemTime::now);
let code = self.calculate_code(&cred, timestamp_sys)?;
let mut refreshable_cred = RefreshableOathCredential::new(cred.to_owned(), self);
refreshable_cred.force_update(Some(code), timestamp);
Ok(refreshable_cred)
}
pub fn calculate_code(
&self,
cred: OathCredential,
cred: &OathCredential,
timestamp_sys: Option<SystemTime>,
) -> Result<OathCodeDisplay, Error> {
if self.locked {