use crate::{
agents::{decode_base64, ForAgent},
errors::AtomicResult,
urls,
utils::check_timestamp_in_past,
Storelike,
};
#[derive(serde::Deserialize)]
pub struct AuthValues {
#[serde(rename = "https://atomicdata.dev/properties/auth/publicKey")]
pub public_key: String,
#[serde(rename = "https://atomicdata.dev/properties/auth/timestamp")]
pub timestamp: i64,
#[serde(rename = "https://atomicdata.dev/properties/auth/signature")]
pub signature: String,
#[serde(rename = "https://atomicdata.dev/properties/auth/requestedSubject")]
pub requested_subject: String,
#[serde(rename = "https://atomicdata.dev/properties/auth/agent")]
pub agent_subject: String,
}
#[tracing::instrument(skip_all)]
pub fn check_auth_signature(subject: &str, auth_header: &AuthValues) -> AtomicResult<()> {
let agent_pubkey = decode_base64(&auth_header.public_key)?;
let message = format!("{} {}", subject, &auth_header.timestamp);
let peer_public_key =
ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, agent_pubkey);
let signature_bytes = decode_base64(&auth_header.signature)?;
peer_public_key
.verify(message.as_bytes(), &signature_bytes)
.map_err(|_e| {
format!(
"Incorrect signature for auth headers. This could be due to an error during signing or serialization of the commit. Compare this to the serialized message in the client: {}",
message,
)
})?;
Ok(())
}
const ACCEPTABLE_TIME_DIFFERENCE: i64 = 10000;
#[tracing::instrument(skip_all)]
pub fn get_agent_from_auth_values_and_check(
auth_header_values: Option<AuthValues>,
store: &impl Storelike,
) -> AtomicResult<ForAgent> {
if let Some(auth_vals) = auth_header_values {
check_auth_signature(&auth_vals.requested_subject, &auth_vals)
.map_err(|e| format!("Error checking authentication headers. {}", e))?;
check_timestamp_in_past(auth_vals.timestamp, ACCEPTABLE_TIME_DIFFERENCE)?;
let found_public_key = store.get_value(&auth_vals.agent_subject, urls::PUBLIC_KEY)?;
if found_public_key.to_string() != auth_vals.public_key {
Err(
"The public key in the auth headers does not match the public key in the agent"
.to_string()
.into(),
)
} else {
Ok(ForAgent::AgentSubject(auth_vals.agent_subject))
}
} else {
Ok(ForAgent::Public)
}
}