use std::collections::HashMap;
use sha2::{Digest, Sha256};
use crate::base64::base64url_decode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthState {
Seen = 0,
Verified = 1,
Changed = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthringType {
Ed25519,
Cu25519,
Rsa,
}
#[derive(Debug, Clone)]
pub struct AuthEntry {
pub fingerprint: Vec<u8>,
pub state: AuthState,
}
#[derive(Debug, Clone, Default)]
pub struct AuthRing {
pub entries: HashMap<String, AuthEntry>,
}
impl AuthRing {
pub fn compute_fingerprint(pubkey: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(pubkey);
hasher.finalize().to_vec()
}
pub fn update(&mut self, handle_b64: &str, pubkey: &[u8], verified: bool) -> AuthState {
let fp = Self::compute_fingerprint(pubkey);
match self.entries.get_mut(handle_b64) {
Some(entry) => {
if entry.fingerprint == fp {
if verified {
entry.state = AuthState::Verified;
}
entry.state
} else {
entry.fingerprint = fp;
entry.state = if verified {
AuthState::Verified
} else {
AuthState::Changed
};
entry.state
}
}
None => {
let state = if verified {
AuthState::Verified
} else {
AuthState::Seen
};
self.entries.insert(
handle_b64.to_string(),
AuthEntry {
fingerprint: fp,
state,
},
);
state
}
}
}
pub fn get_state(&self, handle_b64: &str) -> Option<AuthState> {
self.entries.get(handle_b64).map(|e| e.state)
}
pub fn merge_union(&mut self, other: &AuthRing) {
for (h, e) in &other.entries {
self.entries.entry(h.clone()).or_insert_with(|| e.clone());
}
}
pub fn serialize_ltlv(&self) -> Vec<u8> {
let mut out = Vec::new();
for (tag, entry) in &self.entries {
if tag.len() > 255 {
continue;
}
let mut value = Vec::with_capacity(1 + entry.fingerprint.len());
value.push(entry.state as u8);
value.extend_from_slice(&entry.fingerprint);
out.push(tag.len() as u8);
out.extend_from_slice(tag.as_bytes());
if value.len() < 0xFFFF {
out.extend_from_slice(&(value.len() as u16).to_be_bytes());
} else {
out.extend_from_slice(&0xFFFFu16.to_be_bytes());
out.extend_from_slice(&(value.len() as u32).to_be_bytes());
}
out.extend_from_slice(&value);
}
out
}
pub fn is_tracked(&self, handle_b64: &str) -> bool {
self.entries.contains_key(handle_b64)
}
pub fn tracked_users(&self) -> Vec<&str> {
self.entries.keys().map(String::as_str).collect()
}
pub fn get_auth_method(&self, handle_b64: &str) -> Option<AuthState> {
self.get_state(handle_b64)
}
pub fn fingerprint(pubkey: &[u8]) -> Vec<u8> {
Self::compute_fingerprint(pubkey)
}
pub fn deserialize_ltlv(data: &[u8]) -> Self {
let mut ring = AuthRing::default();
let mut offset = 0usize;
while offset < data.len() {
if offset >= data.len() {
break;
}
let tag_len = data[offset] as usize;
offset += 1;
if offset + tag_len > data.len() {
break;
}
let tag = String::from_utf8_lossy(&data[offset..offset + tag_len]).to_string();
offset += tag_len;
if offset + 2 > data.len() {
break;
}
let len16 = u16::from_be_bytes([data[offset], data[offset + 1]]);
offset += 2;
let mut val_len = len16 as usize;
if len16 == 0xFFFF {
if offset + 4 > data.len() {
break;
}
let len32 = u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
val_len = len32 as usize;
offset += 4;
}
if offset + val_len > data.len() || val_len == 0 {
break;
}
let value = &data[offset..offset + val_len];
offset += val_len;
let state = match value[0] {
1 => AuthState::Verified,
2 => AuthState::Changed,
_ => AuthState::Seen,
};
let fp = value[1..].to_vec();
ring.entries.insert(
tag,
AuthEntry {
fingerprint: fp,
state,
},
);
}
ring
}
}
impl AuthringType {
pub fn from_key_type(key_type: &str) -> Option<Self> {
match key_type {
"Ed25519" => Some(Self::Ed25519),
"Cu25519" => Some(Self::Cu25519),
"RSA" => Some(Self::Rsa),
_ => None,
}
}
pub fn from_signature_type(sig_type: &str) -> Option<Self> {
match sig_type {
"Ed25519" => Some(Self::Ed25519),
"RSA" => Some(Self::Rsa),
_ => None,
}
}
}
pub fn is_authring_attr(attr: &str) -> bool {
matches!(attr, "*!authring" | "*!authCu255" | "*!authRSA")
}
pub fn normalize_handle(input: &str) -> Option<String> {
if input.len() == 11
&& base64url_decode(input)
.map(|v| v.len() == 8)
.unwrap_or(false)
{
Some(input.to_string())
} else {
None
}
}