mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-04 05:44: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",
|
"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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -120,6 +132,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"apdu-core",
|
"apdu-core",
|
||||||
"base64",
|
"base64",
|
||||||
|
"getrandom",
|
||||||
"hmac",
|
"hmac",
|
||||||
"iso7816-tlv",
|
"iso7816-tlv",
|
||||||
"ouroboros",
|
"ouroboros",
|
||||||
|
@ -331,6 +344,88 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
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]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apdu-core = "0.4.0"
|
apdu-core = "0.4.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
getrandom = "0.3.1"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
iso7816-tlv = "0.4.4"
|
iso7816-tlv = "0.4.4"
|
||||||
ouroboros = "0.18.5"
|
ouroboros = "0.18.5"
|
||||||
|
|
|
@ -30,7 +30,12 @@ fn main() {
|
||||||
for yubikey in yubikeys {
|
for yubikey in yubikeys {
|
||||||
let device_label: &str = yubikey;
|
let device_label: &str = yubikey;
|
||||||
println!("Found device with label {}", device_label);
|
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());
|
println!("YubiKey version is {:?}", session.get_version());
|
||||||
for c in session.list_oath_codes().unwrap() {
|
for c in session.list_oath_codes().unwrap() {
|
||||||
println!("{}", c);
|
println!("{}", c);
|
||||||
|
@ -49,7 +54,7 @@ fn main() {
|
||||||
println!("No credentials on device {}", device_label);
|
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
|
// Enumerate the OATH codes
|
||||||
for oath in 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()
|
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> {
|
fn _hmac_shorten_key(key: &[u8], algo: HashAlgo) -> Vec<u8> {
|
||||||
if key.len() > algo.digest_size() {
|
if key.len() > algo.digest_size() {
|
||||||
algo.get_hash_fun()(key)
|
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 struct RefreshableOathCredential<'a> {
|
||||||
pub cred: OathCredential,
|
pub cred: OathCredential,
|
||||||
pub code: Option<OathCodeDisplay>,
|
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 {
|
impl OathSession {
|
||||||
pub fn new(name: &str) -> Result<Self, Error> {
|
pub fn new(name: &str) -> Result<Self, Error> {
|
||||||
let transaction_context = TransactionContext::from_name(name)?;
|
let transaction_context = TransactionContext::from_name(name)?;
|
||||||
|
@ -142,7 +139,9 @@ impl OathSession {
|
||||||
println!("{:?}: {:?}", tag, data);
|
println!("{:?}: {:?}", tag, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let challenge = info_map.get(&(Tag::Challenge as u8)).map(Vec::to_owned);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
locked: challenge.is_some(),
|
||||||
version: info_map
|
version: info_map
|
||||||
.get(&(Tag::Version as u8))
|
.get(&(Tag::Version as u8))
|
||||||
.unwrap_or(&vec![0u8; 0])
|
.unwrap_or(&vec![0u8; 0])
|
||||||
|
@ -151,10 +150,7 @@ impl OathSession {
|
||||||
.get(&(Tag::Name as u8))
|
.get(&(Tag::Name as u8))
|
||||||
.unwrap_or(&vec![0u8; 0])
|
.unwrap_or(&vec![0u8; 0])
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
challenge: info_map
|
challenge,
|
||||||
.get(&(Tag::Challenge as u8))
|
|
||||||
.unwrap_or(&vec![0u8; 0])
|
|
||||||
.to_owned(),
|
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
transaction_context,
|
transaction_context,
|
||||||
})
|
})
|
||||||
|
@ -164,6 +160,76 @@ impl OathSession {
|
||||||
&self.version
|
&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(
|
pub fn rename_credential(
|
||||||
&self,
|
&self,
|
||||||
old: CredentialIDData,
|
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(
|
pub fn calculate_code(
|
||||||
&self,
|
&self,
|
||||||
cred: OathCredential,
|
cred: OathCredential,
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub enum Error {
|
||||||
Pcsc(pcsc::Error),
|
Pcsc(pcsc::Error),
|
||||||
Parsing(String),
|
Parsing(String),
|
||||||
DeviceMismatch,
|
DeviceMismatch,
|
||||||
|
FailedAuthentication,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
@ -46,6 +47,7 @@ impl Display for Error {
|
||||||
Self::Pcsc(error) => f.write_fmt(format_args!("{}", error)),
|
Self::Pcsc(error) => f.write_fmt(format_args!("{}", error)),
|
||||||
Self::Parsing(msg) => f.write_str(msg),
|
Self::Parsing(msg) => f.write_str(msg),
|
||||||
Self::DeviceMismatch => f.write_str("Devices do not match"),
|
Self::DeviceMismatch => f.write_str("Devices do not match"),
|
||||||
|
Error::FailedAuthentication => f.write_str("Authentication failure"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue