use crate::primitives::PublicKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub const AUTH_VERSION: &str = "0.1";
pub const AUTH_PROTOCOL_ID: &str = "auth message signature";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MessageType {
InitialRequest,
InitialResponse,
CertificateRequest,
CertificateResponse,
General,
}
impl MessageType {
pub fn as_str(&self) -> &'static str {
match self {
Self::InitialRequest => "initialRequest",
Self::InitialResponse => "initialResponse",
Self::CertificateRequest => "certificateRequest",
Self::CertificateResponse => "certificateResponse",
Self::General => "general",
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s {
"initialRequest" => Some(Self::InitialRequest),
"initialResponse" => Some(Self::InitialResponse),
"certificateRequest" => Some(Self::CertificateRequest),
"certificateResponse" => Some(Self::CertificateResponse),
"general" => Some(Self::General),
_ => None,
}
}
pub fn is_handshake(&self) -> bool {
matches!(self, Self::InitialRequest | Self::InitialResponse)
}
pub fn is_certificate(&self) -> bool {
matches!(self, Self::CertificateRequest | Self::CertificateResponse)
}
}
impl std::fmt::Display for MessageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthMessage {
pub version: String,
pub message_type: MessageType,
pub identity_key: PublicKey,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub your_nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub certificates: Option<Vec<crate::auth::certificates::VerifiableCertificate>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub requested_certificates: Option<RequestedCertificateSet>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<Vec<u8>>,
}
impl AuthMessage {
pub fn new(message_type: MessageType, identity_key: PublicKey) -> Self {
Self {
version: AUTH_VERSION.to_string(),
message_type,
identity_key,
nonce: None,
initial_nonce: None,
your_nonce: None,
certificates: None,
requested_certificates: None,
payload: None,
signature: None,
}
}
pub fn signing_data(&self) -> Vec<u8> {
match self.message_type {
MessageType::InitialResponse => {
let mut data = Vec::new();
if let Some(ref your_nonce) = self.your_nonce {
if let Ok(decoded) = crate::primitives::from_base64(your_nonce) {
data.extend_from_slice(&decoded);
}
}
if let Some(ref initial_nonce) = self.initial_nonce {
if let Ok(decoded) = crate::primitives::from_base64(initial_nonce) {
data.extend_from_slice(&decoded);
}
}
data
}
MessageType::General => {
self.payload.clone().unwrap_or_default()
}
MessageType::CertificateRequest => {
if let Some(ref req) = self.requested_certificates {
serde_json::to_vec(req).unwrap_or_default()
} else {
Vec::new()
}
}
MessageType::CertificateResponse => {
if let Some(ref certs) = self.certificates {
serde_json::to_vec(certs).unwrap_or_default()
} else {
Vec::new()
}
}
MessageType::InitialRequest => {
Vec::new()
}
}
}
pub fn get_key_id(&self, peer_session_nonce: Option<&str>) -> String {
let nonce = self.nonce.as_deref().unwrap_or("");
let peer_nonce = peer_session_nonce.unwrap_or("");
match self.message_type {
MessageType::InitialResponse => {
let your = self.your_nonce.as_deref().unwrap_or("");
let initial = self.initial_nonce.as_deref().unwrap_or("");
format!("{} {}", your, initial)
}
_ => {
format!("{} {}", nonce, peer_nonce)
}
}
}
pub fn validate(&self) -> crate::Result<()> {
if self.version != AUTH_VERSION {
return Err(crate::Error::AuthError(format!(
"Invalid auth version: expected {}, got {}",
AUTH_VERSION, self.version
)));
}
match self.message_type {
MessageType::InitialRequest => {
if self.initial_nonce.is_none() {
return Err(crate::Error::AuthError(
"InitialRequest must have an initial_nonce".into(),
));
}
}
MessageType::InitialResponse => {
if self.nonce.is_none() && self.initial_nonce.is_none() {
return Err(crate::Error::AuthError(
"InitialResponse must have nonce or initial_nonce".into(),
));
}
if self.your_nonce.is_none() {
return Err(crate::Error::AuthError(
"InitialResponse must have your_nonce".into(),
));
}
if self.signature.is_none() {
return Err(crate::Error::AuthError(
"InitialResponse must have signature".into(),
));
}
}
MessageType::CertificateRequest => {
if self.requested_certificates.is_none() {
return Err(crate::Error::AuthError(
"CertificateRequest must have requested_certificates".into(),
));
}
}
MessageType::CertificateResponse => {
if self.certificates.is_none() {
return Err(crate::Error::AuthError(
"CertificateResponse must have certificates".into(),
));
}
}
MessageType::General => {
if self.signature.is_none() {
return Err(crate::Error::AuthError(
"General message must have signature".into(),
));
}
}
}
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PeerSession {
pub is_authenticated: bool,
pub session_nonce: Option<String>,
pub peer_nonce: Option<String>,
pub peer_identity_key: Option<PublicKey>,
pub last_update: u64,
pub certificates_required: bool,
pub certificates_validated: bool,
}
impl PeerSession {
pub fn new() -> Self {
Self::default()
}
pub fn with_nonce(session_nonce: String) -> Self {
Self {
session_nonce: Some(session_nonce),
last_update: current_time_ms(),
..Default::default()
}
}
pub fn touch(&mut self) {
self.last_update = current_time_ms();
}
pub fn is_ready(&self) -> bool {
self.is_authenticated && (!self.certificates_required || self.certificates_validated)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RequestedCertificateSet {
#[serde(default)]
pub certifiers: Vec<String>,
#[serde(default)]
pub types: HashMap<String, Vec<String>>,
}
impl RequestedCertificateSet {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.certifiers.is_empty() && self.types.is_empty()
}
pub fn add_certifier(&mut self, certifier_hex: impl Into<String>) {
self.certifiers.push(certifier_hex.into());
}
pub fn add_type(&mut self, type_id: impl Into<String>, fields: Vec<String>) {
self.types.insert(type_id.into(), fields);
}
pub fn is_certifier_trusted(&self, certifier_hex: &str) -> bool {
self.certifiers.is_empty() || self.certifiers.contains(&certifier_hex.to_string())
}
pub fn is_type_requested(&self, type_id: &str) -> bool {
self.types.is_empty() || self.types.contains_key(type_id)
}
pub fn get_fields_for_type(&self, type_id: &str) -> Option<&Vec<String>> {
self.types.get(type_id)
}
}
pub fn current_time_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::PrivateKey;
#[test]
fn test_message_type_conversion() {
assert_eq!(MessageType::InitialRequest.as_str(), "initialRequest");
assert_eq!(
MessageType::from_str("initialRequest"),
Some(MessageType::InitialRequest)
);
assert_eq!(MessageType::from_str("invalid"), None);
}
#[test]
fn test_auth_message_creation() {
let key = PrivateKey::random().public_key();
let msg = AuthMessage::new(MessageType::InitialRequest, key.clone());
assert_eq!(msg.version, AUTH_VERSION);
assert_eq!(msg.message_type, MessageType::InitialRequest);
assert_eq!(msg.identity_key, key);
assert!(msg.nonce.is_none());
}
#[test]
fn test_peer_session() {
let mut session = PeerSession::with_nonce("test-nonce".to_string());
assert_eq!(session.session_nonce.as_deref(), Some("test-nonce"));
assert!(!session.is_authenticated);
assert!(!session.is_ready());
session.is_authenticated = true;
assert!(session.is_ready());
session.certificates_required = true;
assert!(!session.is_ready());
session.certificates_validated = true;
assert!(session.is_ready());
}
#[test]
fn test_requested_certificate_set() {
let mut req = RequestedCertificateSet::new();
assert!(req.is_empty());
req.add_certifier("02abc123");
req.add_type("type1", vec!["name".to_string(), "email".to_string()]);
assert!(!req.is_empty());
assert!(req.is_certifier_trusted("02abc123"));
assert!(!req.is_certifier_trusted("02def456"));
assert!(req.is_type_requested("type1"));
assert!(!req.is_type_requested("type2"));
}
}