use super::{symmetric::Symmetric, *};
use crate::ObjectId;
use ring::aead;
use secrecy::{ExposeSecret, SecretString};
use std::mem::size_of;
pub use yubico_manager;
use yubico_manager::Yubico;
type Nonce = [u8; 12];
type Challenge = [u8; 64];
type Response = [u8; 20];
const HEADER_PAYLOAD: usize =
size_of::<SealedHeader>() - size_of::<Tag>() - size_of::<Nonce>() - size_of::<Challenge>();
const HEADER_CYPHERTEXT: usize =
size_of::<SealedHeader>() - size_of::<Nonce>() - size_of::<Challenge>();
pub type YubikeyCR = KeyingScheme<YubikeyHeader, Symmetric>;
impl YubikeyCR {
pub fn with_credentials(
username: SecretString,
password: SecretString,
ykconfig: yubico_manager::config::Config,
) -> Result<Self> {
let master_key = derive_argon2(
b"zerostash.com yubikey cr master key",
username.expose_secret().as_bytes(),
password.expose_secret().as_bytes(),
)?;
Ok(Self::new(
YubikeyHeader {
master_key,
ykconfig,
},
Symmetric::random()?,
))
}
}
pub(crate) use private::*;
mod private {
use super::*;
pub struct YubikeyHeader {
pub(super) master_key: RawKey,
pub(super) ykconfig: yubico_manager::config::Config,
}
fn header_key(
master_key: &RawKey,
challenge: Challenge,
config: yubico_manager::config::Config,
) -> Result<RawKey> {
let mut k = [0; KEY_SIZE + size_of::<Response>()];
let mut yk = Yubico::new();
let resp = yk
.challenge_response_hmac(&challenge, config)
.map_err(|_| CryptoError::Fatal)?
.0;
k[..KEY_SIZE].copy_from_slice(master_key.expose_secret());
k[KEY_SIZE..].copy_from_slice(&resp);
Ok(blake3::derive_key("zerostash.com 2022 yubikey challenge-response", &k).into())
}
impl HeaderScheme for YubikeyHeader {
fn open_root(&self, header: SealedHeader) -> Result<OpenHeader> {
let mut sealed = header.0;
let mut challenge = [0; size_of::<Challenge>()];
challenge.copy_from_slice(&sealed[HEADER_CYPHERTEXT + size_of::<Nonce>()..]);
let aead = get_aead(header_key(
&self.master_key,
challenge,
self.ykconfig.clone(),
)?);
let nonce = {
let mut buf = Nonce::default();
buf.copy_from_slice(
&sealed[HEADER_CYPHERTEXT..HEADER_CYPHERTEXT + size_of::<Nonce>()],
);
aead::Nonce::assume_unique_for_key(buf)
};
let _ = aead
.open_in_place(nonce, aead::Aad::empty(), &mut sealed[..HEADER_CYPHERTEXT])
.map_err(CryptoError::from)?;
Ok(OpenHeader(sealed))
}
fn seal_root(&self, header: OpenHeader) -> Result<SealedHeader> {
let mut output = header.0;
let random = SystemRandom::new();
let nonce = {
let mut buf = Nonce::default();
random.fill(&mut buf)?;
aead::Nonce::assume_unique_for_key(buf)
};
let challenge = {
let mut buf = [0; size_of::<Challenge>()];
random.fill(&mut buf)?;
buf
};
output[HEADER_CYPHERTEXT..HEADER_CYPHERTEXT + size_of::<Nonce>()]
.copy_from_slice(nonce.as_ref());
let aead = get_aead(header_key(
&self.master_key,
challenge,
self.ykconfig.clone(),
)?);
let tag = aead.seal_in_place_separate_tag(
nonce,
aead::Aad::empty(),
&mut output[..HEADER_PAYLOAD],
)?;
output[HEADER_PAYLOAD..HEADER_PAYLOAD + size_of::<Tag>()].copy_from_slice(tag.as_ref());
output[HEADER_CYPHERTEXT + size_of::<Nonce>()..].copy_from_slice(&challenge);
Ok(SealedHeader(output))
}
fn root_object_id(&self) -> Result<ObjectId> {
super::symmetric::root_object_id(&self.master_key)
}
}
}
#[cfg(test)]
mod test {
use crate::crypto::Scheme;
use std::sync::Arc;
#[test]
#[ignore]
fn userpass_encrypt_decrypt() {
use super::YubikeyCR;
use crate::chunks::RawChunkPointer;
use yubico_manager::{config::Config, Yubico};
let mut yubi = Yubico::new();
let ykconfig = if let Ok(device) = yubi.find_yubikey() {
Config::default()
.set_vendor_id(device.vendor_id)
.set_product_id(device.product_id)
} else {
panic!("No Yubikey")
};
let seal_key = YubikeyCR::with_credentials(
"test".to_string().into(),
"test".to_string().into(),
ykconfig.clone(),
)
.unwrap();
let header = seal_key.seal_root(&Default::default()).unwrap();
let open_key = YubikeyCR::with_credentials(
"test".to_string().into(),
"test".to_string().into(),
ykconfig,
)
.unwrap();
let open_header = Arc::new(open_key).open_root(header).unwrap();
assert_eq!(open_header.root_ptr, RawChunkPointer::default());
}
}