feat(wasm): attachment / generator / totp / imgsecret / id bridges
Also ports TOTP RFC 6238 compute to relicario-core::item_types::totp so native + CLI + WASM share one implementation (audit H5: CSPRNG via core's Uniform-sampling generator). Adds hmac = "0.12" and sha1 = "0.10" to relicario-core deps to support HOTP/TOTP HMAC with Sha1/Sha256/Sha512. RFC 6238 test vector (t=59, SHA-1, 8 digits) passes: "94287082".
This commit is contained in:
@@ -12,6 +12,8 @@ argon2 = "0.5"
|
||||
chacha20poly1305 = "0.10"
|
||||
rand = "0.8"
|
||||
sha2 = "0.10"
|
||||
sha1 = "0.10"
|
||||
hmac = "0.12"
|
||||
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ pub use identity::IdentityCore;
|
||||
pub use card::{CardCore, CardKind};
|
||||
pub use key::KeyCore;
|
||||
pub use document::DocumentCore;
|
||||
pub use totp::{TotpCore, TotpConfig, TotpAlgorithm, TotpKind};
|
||||
pub use totp::{TotpCore, TotpConfig, TotpAlgorithm, TotpKind, compute_totp_code};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
//! TOTP: standalone 2FA item type. Also reused as TotpConfig field on Login.
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha1::Sha1 as HmacSha1;
|
||||
use sha2::{Sha256 as HmacSha256, Sha512 as HmacSha512};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::error::{RelicarioError, Result};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct TotpCore {
|
||||
pub config: TotpConfig,
|
||||
@@ -55,6 +60,63 @@ impl Default for TotpKind {
|
||||
fn default() -> Self { TotpKind::Totp }
|
||||
}
|
||||
|
||||
/// Compute a TOTP/HOTP/Steam code for `config` at the given Unix timestamp.
|
||||
///
|
||||
/// For TOTP and Steam: counter = `now_unix_seconds / period_seconds`.
|
||||
/// For HOTP: uses the `counter` carried in the variant.
|
||||
pub fn compute_totp_code(config: &TotpConfig, now_unix_seconds: u64) -> Result<String> {
|
||||
let counter = match config.kind {
|
||||
TotpKind::Totp => now_unix_seconds / config.period_seconds as u64,
|
||||
TotpKind::Hotp { counter } => counter,
|
||||
TotpKind::Steam => now_unix_seconds / config.period_seconds as u64,
|
||||
};
|
||||
let counter_bytes = counter.to_be_bytes();
|
||||
let hmac_out: Vec<u8> = match config.algorithm {
|
||||
TotpAlgorithm::Sha1 => {
|
||||
let mut mac = Hmac::<HmacSha1>::new_from_slice(&config.secret)
|
||||
.map_err(|e| RelicarioError::Format(format!("hmac: {e}")))?;
|
||||
mac.update(&counter_bytes);
|
||||
mac.finalize().into_bytes().to_vec()
|
||||
}
|
||||
TotpAlgorithm::Sha256 => {
|
||||
let mut mac = Hmac::<HmacSha256>::new_from_slice(&config.secret)
|
||||
.map_err(|e| RelicarioError::Format(format!("hmac: {e}")))?;
|
||||
mac.update(&counter_bytes);
|
||||
mac.finalize().into_bytes().to_vec()
|
||||
}
|
||||
TotpAlgorithm::Sha512 => {
|
||||
let mut mac = Hmac::<HmacSha512>::new_from_slice(&config.secret)
|
||||
.map_err(|e| RelicarioError::Format(format!("hmac: {e}")))?;
|
||||
mac.update(&counter_bytes);
|
||||
mac.finalize().into_bytes().to_vec()
|
||||
}
|
||||
};
|
||||
let offset = (hmac_out[hmac_out.len() - 1] & 0x0F) as usize;
|
||||
let truncated = ((hmac_out[offset] as u32 & 0x7F) << 24)
|
||||
| ((hmac_out[offset + 1] as u32) << 16)
|
||||
| ((hmac_out[offset + 2] as u32) << 8)
|
||||
| (hmac_out[offset + 3] as u32);
|
||||
let modulus = 10u32.pow(config.digits as u32);
|
||||
Ok(format!("{:0width$}", truncated % modulus, width = config.digits as usize))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod compute_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rfc6238_sha1_vector_59() {
|
||||
let cfg = TotpConfig {
|
||||
secret: Zeroizing::new(b"12345678901234567890".to_vec()),
|
||||
algorithm: TotpAlgorithm::Sha1,
|
||||
digits: 8,
|
||||
period_seconds: 30,
|
||||
kind: TotpKind::Totp,
|
||||
};
|
||||
assert_eq!(compute_totp_code(&cfg, 59).unwrap(), "94287082");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user