use secrecy::{ExposeSecret, SecretBox};
#[cfg(feature = "_challenge_response")]
use crate::challenge_response::ChallengeResponseSlot;
#[cfg(feature = "_challenge_response")]
use crate::hash::SHA256_DIGEST_SIZE;
#[cfg(feature = "_challenge_response")]
use challenge_response::{
ChallengeResponse,
config::{Config, Mode},
};
#[cfg(feature = "_challenge_response")]
use hkdf::Hkdf;
#[cfg(feature = "_challenge_response")]
use sha2::Sha256;
#[cfg(feature = "_challenge_response")]
const HKDF_INFO: &[u8] = b"luks-rs challenge-response";
pub struct UnlockKey {
password: SecretBox<Vec<u8>>,
#[cfg(feature = "_challenge_response")]
challenge_response: Option<ChallengeResponseKey>,
}
#[cfg(feature = "_challenge_response")]
pub enum ChallengeResponseKey {
Hardware {
serial: Option<u32>,
slot: ChallengeResponseSlot,
},
Software {
secret: SecretBox<Vec<u8>>,
},
}
impl UnlockKey {
pub fn from_passphrase(passphrase: String) -> Self {
Self {
password: SecretBox::new(Box::new(passphrase.into_bytes())),
#[cfg(feature = "_challenge_response")]
challenge_response: None,
}
}
#[cfg(feature = "_challenge_response")]
pub fn with_challenge_response(mut self, serial: Option<u32>, slot: ChallengeResponseSlot) -> Self {
self.challenge_response = Some(ChallengeResponseKey::Hardware { serial, slot });
self
}
#[cfg(feature = "_challenge_response")]
pub fn with_software_challenge_response(mut self, secret: Vec<u8>) -> Self {
self.challenge_response = Some(ChallengeResponseKey::Software {
secret: SecretBox::new(Box::new(secret)),
});
self
}
pub fn expose_bytes(&self) -> &[u8] {
self.password.expose_secret().as_slice()
}
#[cfg(feature = "_challenge_response")]
pub fn challenge_response(&self) -> Option<&ChallengeResponseKey> {
self.challenge_response.as_ref()
}
pub fn calculate_effective_key(&self, challenge: &[u8]) -> Result<Vec<u8>, crate::LuksError> {
#[cfg(feature = "_challenge_response")]
{
let cr_key = match &self.challenge_response {
Some(y) => y,
None => return Ok(self.password.expose_secret().to_vec()),
};
let response = match cr_key {
ChallengeResponseKey::Hardware { serial, slot } => {
let mut cr = ChallengeResponse::new().map_err(|e| {
crate::LuksError::ChallengeResponse(format!(
"Failed to initialize challenge-response: {}",
e
))
})?;
let device = match serial {
Some(s) => cr.find_device_from_serial(*s).map_err(|e| {
crate::LuksError::ChallengeResponse(format!(
"Failed to find device with serial {}: {}",
s, e
))
})?,
None => cr.find_device().map_err(|e| {
crate::LuksError::ChallengeResponse(format!("Failed to find device: {}", e))
})?,
};
let config = Config::new_from(device)
.set_variable_size(true)
.set_mode(Mode::Sha1)
.set_slot((*slot).into());
cr.challenge_response_hmac(challenge, config)
.map_err(|e| {
crate::LuksError::ChallengeResponse(format!("Challenge-response failed: {}", e))
})?
.to_vec()
}
ChallengeResponseKey::Software { secret } => {
use hmac::{Hmac, Mac};
use sha1::Sha1;
let mut mac = Hmac::<Sha1>::new_from_slice(secret.expose_secret())
.map_err(|e| crate::LuksError::ChallengeResponse(format!("Invalid HMAC key: {}", e)))?;
mac.update(challenge);
mac.finalize().into_bytes().to_vec()
}
};
let hk = Hkdf::<Sha256>::new(Some(&response), self.password.expose_secret());
let mut derived_key = [0u8; SHA256_DIGEST_SIZE];
hk.expand(HKDF_INFO, &mut derived_key)
.map_err(|e| crate::LuksError::ChallengeResponse(format!("HKDF expansion failed: {}", e)))?;
Ok(derived_key.to_vec())
}
#[cfg(not(feature = "_challenge_response"))]
{
let _ = challenge;
Ok(self.password.expose_secret().to_vec())
}
}
}
#[cfg(all(test, feature = "_challenge_response"))]
mod tests {
use super::*;
#[cfg(feature = "_challenge_response")]
#[test]
fn test_software_challenge_response() {
let password = "test-password".to_string();
let hardware_secret = vec![0x01, 0x02, 0x03, 0x04];
let challenge = vec![0xAA, 0xBB, 0xCC, 0xDD];
let key = UnlockKey::from_passphrase(password.clone())
.with_software_challenge_response(hardware_secret.clone());
let effective_key = key.calculate_effective_key(&challenge).unwrap();
use hmac::{Hmac, Mac};
use sha1::Sha1;
let mut mac = Hmac::<Sha1>::new_from_slice(&hardware_secret).unwrap();
mac.update(&challenge);
let expected_response = mac.finalize().into_bytes();
let hk = hkdf::Hkdf::<sha2::Sha256>::new(Some(&expected_response), password.as_bytes());
let mut expected_derived_key = [0u8; SHA256_DIGEST_SIZE];
hk.expand(HKDF_INFO, &mut expected_derived_key).unwrap();
assert_eq!(effective_key, expected_derived_key);
}
}
impl From<String> for UnlockKey {
fn from(passphrase: String) -> Self {
Self::from_passphrase(passphrase)
}
}
impl From<&str> for UnlockKey {
fn from(passphrase: &str) -> Self {
Self::from_passphrase(passphrase.to_string())
}
}
pub struct VolumeKey(SecretBox<Vec<u8>>);
impl VolumeKey {
pub fn new(bytes: Vec<u8>) -> Result<Self, crate::LuksError> {
if bytes.len() != crate::AES128_KEY_SIZE * 2 && bytes.len() != crate::AES256_KEY_SIZE * 2 {
return Err(crate::LuksError::InvalidHeader(format!(
"Invalid volume key size: expected {} or {}, got {}",
crate::AES128_KEY_SIZE * 2,
crate::AES256_KEY_SIZE * 2,
bytes.len()
)));
}
Ok(Self(SecretBox::new(Box::new(bytes))))
}
pub fn expose_bytes(&self) -> &[u8] {
self.0.expose_secret().as_slice()
}
}