mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-03 21:34:40 +01:00
feat: add passphrase support
This commit is contained in:
parent
ef953aed58
commit
1ffd5b1c87
5 changed files with 192 additions and 18 deletions
95
Cargo.lock
generated
95
Cargo.lock
generated
|
@ -90,6 +90,18 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
|
@ -120,6 +132,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"apdu-core",
|
||||
"base64",
|
||||
"getrandom",
|
||||
"hmac",
|
||||
"iso7816-tlv",
|
||||
"ouroboros",
|
||||
|
@ -331,6 +344,88 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[dependencies]
|
||||
apdu-core = "0.4.0"
|
||||
base64 = "0.22.1"
|
||||
getrandom = "0.3.1"
|
||||
hmac = "0.12.1"
|
||||
iso7816-tlv = "0.4.4"
|
||||
ouroboros = "0.18.5"
|
||||
|
|
|
@ -30,7 +30,12 @@ fn main() {
|
|||
for yubikey in yubikeys {
|
||||
let device_label: &str = yubikey;
|
||||
println!("Found device with label {}", device_label);
|
||||
let session = OathSession::new(yubikey).unwrap();
|
||||
let mut session = OathSession::new(yubikey).unwrap();
|
||||
|
||||
// session.set_key(&session.derive_key("1234")).unwrap();
|
||||
// session.unlock_session(&session.derive_key("1234")).unwrap();
|
||||
// session.unset_key().unwrap();
|
||||
|
||||
println!("YubiKey version is {:?}", session.get_version());
|
||||
for c in session.list_oath_codes().unwrap() {
|
||||
println!("{}", c);
|
||||
|
@ -49,7 +54,7 @@ fn main() {
|
|||
println!("No credentials on device {}", device_label);
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(5)); // show refresh is working
|
||||
std::thread::sleep(std::time::Duration::from_secs(0)); // show refresh is working
|
||||
|
||||
// Enumerate the OATH codes
|
||||
for oath in codes {
|
||||
|
|
103
src/lib.rs
103
src/lib.rs
|
@ -32,10 +32,6 @@ fn _hmac_sha1(key: &[u8], message: &[u8]) -> Vec<u8> {
|
|||
mac.finalize().into_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn _derive_key(salt: &[u8], passphrase: &str) -> Vec<u8> {
|
||||
pbkdf2::pbkdf2_hmac_array::<sha1::Sha1, 16>(passphrase.as_bytes(), salt, 1000).to_vec()
|
||||
}
|
||||
|
||||
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
|
||||
if key.len() > algo.digest_size() {
|
||||
algo.get_hash_fun()(key)
|
||||
|
@ -44,14 +40,6 @@ fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct OathSession {
|
||||
version: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
challenge: Vec<u8>,
|
||||
transaction_context: TransactionContext,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct RefreshableOathCredential<'a> {
|
||||
pub cred: OathCredential,
|
||||
pub code: Option<OathCodeDisplay>,
|
||||
|
@ -130,6 +118,15 @@ impl<'a> RefreshableOathCredential<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct OathSession {
|
||||
version: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
challenge: Option<Vec<u8>>,
|
||||
transaction_context: TransactionContext,
|
||||
pub locked: bool,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl OathSession {
|
||||
pub fn new(name: &str) -> Result<Self, Error> {
|
||||
let transaction_context = TransactionContext::from_name(name)?;
|
||||
|
@ -142,7 +139,9 @@ impl OathSession {
|
|||
println!("{:?}: {:?}", tag, data);
|
||||
}
|
||||
|
||||
let challenge = info_map.get(&(Tag::Challenge as u8)).map(Vec::to_owned);
|
||||
Ok(Self {
|
||||
locked: challenge.is_some(),
|
||||
version: info_map
|
||||
.get(&(Tag::Version as u8))
|
||||
.unwrap_or(&vec![0u8; 0])
|
||||
|
@ -151,10 +150,7 @@ impl OathSession {
|
|||
.get(&(Tag::Name as u8))
|
||||
.unwrap_or(&vec![0u8; 0])
|
||||
.to_owned(),
|
||||
challenge: info_map
|
||||
.get(&(Tag::Challenge as u8))
|
||||
.unwrap_or(&vec![0u8; 0])
|
||||
.to_owned(),
|
||||
challenge,
|
||||
name: name.to_string(),
|
||||
transaction_context,
|
||||
})
|
||||
|
@ -164,6 +160,76 @@ impl OathSession {
|
|||
&self.version
|
||||
}
|
||||
|
||||
pub fn unlock_session(&mut self, key: &[u8]) -> Result<(), Error> {
|
||||
let chal = match self.challenge.to_owned() {
|
||||
Some(chal) => chal,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if !self.locked {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let hmac = _hmac_sha1(key, &chal);
|
||||
let random_chal = getrandom::u64().unwrap().to_be_bytes(); // FIXME: unwrap
|
||||
let data = &[
|
||||
to_tlv(Tag::Response, &hmac),
|
||||
to_tlv(Tag::Challenge, &random_chal),
|
||||
]
|
||||
.concat();
|
||||
let resp =
|
||||
self.transaction_context
|
||||
.apdu(0, Instruction::Validate as u8, 0, 0, Some(data))?;
|
||||
let verification = _hmac_sha1(key, &random_chal);
|
||||
if tlv_to_map(resp.buf)
|
||||
.get(&(Tag::Response as u8))
|
||||
.map(|v| *v == verification)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.locked = false;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::FailedAuthentication)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_key(&mut self, key: &[u8]) -> Result<(), Error> {
|
||||
let random_chal = getrandom::u64().unwrap().to_be_bytes(); // FIXME: unwrap
|
||||
let hmac = _hmac_sha1(key, &random_chal);
|
||||
let data = &[
|
||||
to_tlv(
|
||||
Tag::Key,
|
||||
&[&[(OathType::Totp as u8) | (HashAlgo::Sha1 as u8); 1], key].concat(),
|
||||
),
|
||||
to_tlv(Tag::Challenge, &random_chal),
|
||||
to_tlv(Tag::Response, &hmac),
|
||||
]
|
||||
.concat();
|
||||
self.transaction_context
|
||||
.apdu(0, Instruction::SetCode as u8, 0, 0, Some(data))?;
|
||||
let info_buffer =
|
||||
self.transaction_context
|
||||
.apdu_read_all(0, INS_SELECT, 0x04, 0, Some(&OATH_AID))?;
|
||||
let info_map = tlv_to_map(info_buffer);
|
||||
self.challenge = info_map.get(&(Tag::Challenge as u8)).map(Vec::to_owned);
|
||||
self.locked = self.challenge.is_some();
|
||||
|
||||
self.unlock_session(key)
|
||||
}
|
||||
|
||||
pub fn unset_key(&mut self) -> Result<(), Error> {
|
||||
self.transaction_context.apdu(
|
||||
0,
|
||||
Instruction::SetCode as u8,
|
||||
0,
|
||||
0,
|
||||
Some(&to_tlv(Tag::Key, &[0u8; 0])),
|
||||
)?;
|
||||
self.locked = false;
|
||||
self.challenge = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rename_credential(
|
||||
&self,
|
||||
old: CredentialIDData,
|
||||
|
@ -190,6 +256,11 @@ impl OathSession {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn derive_key(&self, passphrase: &str) -> Vec<u8> {
|
||||
pbkdf2::pbkdf2_hmac_array::<sha1::Sha1, 16>(passphrase.as_bytes(), &self.salt, 1000)
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
pub fn calculate_code(
|
||||
&self,
|
||||
cred: OathCredential,
|
||||
|
|
|
@ -14,6 +14,7 @@ pub enum Error {
|
|||
Pcsc(pcsc::Error),
|
||||
Parsing(String),
|
||||
DeviceMismatch,
|
||||
FailedAuthentication,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -46,6 +47,7 @@ impl Display for Error {
|
|||
Self::Pcsc(error) => f.write_fmt(format_args!("{}", error)),
|
||||
Self::Parsing(msg) => f.write_str(msg),
|
||||
Self::DeviceMismatch => f.write_str("Devices do not match"),
|
||||
Error::FailedAuthentication => f.write_str("Authentication failure"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue