mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-04-30 16:21:10 +00:00
refactor(types): move UserIdentityDesc
verification into types crate
This commit is contained in:
parent
fac380fe55
commit
82ab7f7d2c
2 changed files with 67 additions and 52 deletions
|
@ -2,6 +2,7 @@ use core::fmt;
|
|||
use std::ops;
|
||||
use std::str::FromStr;
|
||||
|
||||
use ed25519_dalek::SignatureError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use url::{Host, Position, Url};
|
||||
|
@ -20,8 +21,67 @@ pub struct UserIdentityDesc {
|
|||
pub profile: Signed<UserProfile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct VerfiyError(#[from] VerifyErrorImpl);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum VerifyErrorImpl {
|
||||
#[error("profile id_key mismatch")]
|
||||
ProfileIdKeyMismatch,
|
||||
#[error("act_key[{0}] not signed by id_key")]
|
||||
ActKeySigner(usize),
|
||||
#[error("invalid act_key[{0}] signature: {1}")]
|
||||
ActKeySignature(usize, SignatureError),
|
||||
#[error("profile is not signed by any valid act_key")]
|
||||
ProfileSigner,
|
||||
#[error("invalid profile signature: {0}")]
|
||||
ProfileSignature(SignatureError),
|
||||
#[error("id_url is not in the valid list")]
|
||||
MissingIdUrl,
|
||||
}
|
||||
|
||||
impl UserIdentityDesc {
|
||||
pub const WELL_KNOWN_PATH: &str = "/.well-known/blah/identity.json";
|
||||
|
||||
/// Validate signatures of the identity description at given time.
|
||||
pub fn verify(&self, id_url: Option<&IdUrl>, now_timestamp: u64) -> Result<(), VerfiyError> {
|
||||
if self.id_key != self.profile.signee.user.id_key {
|
||||
return Err(VerifyErrorImpl::ProfileIdKeyMismatch.into());
|
||||
}
|
||||
|
||||
let profile_signing_key = &self.profile.signee.user.act_key;
|
||||
let mut profile_signed = false;
|
||||
|
||||
for (i, signed_kdesc) in self.act_keys.iter().enumerate() {
|
||||
let kdesc = &signed_kdesc.signee.payload;
|
||||
// act_key itself is signed by id_key, so both are id_key here.
|
||||
if signed_kdesc.signee.user.id_key != self.id_key
|
||||
|| signed_kdesc.signee.user.act_key != self.id_key
|
||||
{
|
||||
return Err(VerifyErrorImpl::ActKeySigner(i).into());
|
||||
}
|
||||
signed_kdesc
|
||||
.verify()
|
||||
.map_err(|err| VerifyErrorImpl::ActKeySignature(i, err))?;
|
||||
if now_timestamp < kdesc.expire_time && *profile_signing_key == kdesc.act_key {
|
||||
profile_signed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !profile_signed {
|
||||
return Err(VerifyErrorImpl::ProfileSigner.into());
|
||||
}
|
||||
self.profile
|
||||
.verify()
|
||||
.map_err(VerifyErrorImpl::ProfileSignature)?;
|
||||
if let Some(id_url) = id_url {
|
||||
if !self.profile.signee.payload.id_urls.contains(id_url) {
|
||||
return Err(VerifyErrorImpl::MissingIdUrl.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: JWS or alike?
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::num::NonZero;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{anyhow, ensure, Context};
|
||||
use anyhow::{anyhow, ensure};
|
||||
use axum::http::{HeaderMap, HeaderName, StatusCode};
|
||||
use blah_types::identity::{IdUrl, UserIdentityDesc};
|
||||
use blah_types::{
|
||||
get_timestamp, PubKey, Signed, UserRegisterPayload, X_BLAH_DIFFICULTY, X_BLAH_NONCE,
|
||||
};
|
||||
use blah_types::{get_timestamp, Signed, UserRegisterPayload, X_BLAH_DIFFICULTY, X_BLAH_NONCE};
|
||||
use http_body_util::BodyExt;
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::OsRng;
|
||||
|
@ -246,7 +244,11 @@ pub async fn user_register(
|
|||
}
|
||||
};
|
||||
|
||||
if let Err(err) = validate_id_desc(®.id_url, ®.id_key, &id_desc, fetch_time) {
|
||||
if let Err(err) = (|| {
|
||||
ensure!(reg.id_key == id_desc.id_key, "id_key mismatch");
|
||||
id_desc.verify(Some(®.id_url), fetch_time)?;
|
||||
Ok(())
|
||||
})() {
|
||||
return Err(error_response!(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"invalid_id_description",
|
||||
|
@ -314,53 +316,6 @@ pub async fn user_register(
|
|||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
fn validate_id_desc(
|
||||
id_url: &IdUrl,
|
||||
id_key: &PubKey,
|
||||
id_desc: &UserIdentityDesc,
|
||||
now: u64,
|
||||
) -> anyhow::Result<()> {
|
||||
ensure!(*id_key == id_desc.id_key, "id_key mismatch");
|
||||
|
||||
ensure!(
|
||||
*id_key == id_desc.profile.signee.user.id_key,
|
||||
"profile id_key mismatch",
|
||||
);
|
||||
let profile_signing_key = &id_desc.profile.signee.user.act_key;
|
||||
let mut profile_signed = false;
|
||||
|
||||
for (i, signed_kdesc) in id_desc.act_keys.iter().enumerate() {
|
||||
let kdesc = &signed_kdesc.signee.payload;
|
||||
(|| {
|
||||
// act_key itself is signed by id_key, so both are id_key here.
|
||||
ensure!(
|
||||
signed_kdesc.signee.user.id_key == *id_key
|
||||
&& signed_kdesc.signee.user.act_key == *id_key,
|
||||
"not signed by id_key",
|
||||
);
|
||||
signed_kdesc
|
||||
.verify()
|
||||
.context("signature verification failed")?;
|
||||
if now < kdesc.expire_time && *profile_signing_key == kdesc.act_key {
|
||||
profile_signed = true;
|
||||
}
|
||||
Ok(())
|
||||
})()
|
||||
.map_err(|err| anyhow!("invalid act_key[{}] {}: {}", i, kdesc.act_key, err))?;
|
||||
}
|
||||
|
||||
ensure!(profile_signed, "profile is not signed by valid act_keys");
|
||||
id_desc
|
||||
.profile
|
||||
.verify()
|
||||
.context("profile signature verification failed")?;
|
||||
ensure!(
|
||||
id_desc.profile.signee.payload.id_urls == std::slice::from_ref(id_url),
|
||||
"id_url list must consists of a single matching id_url",
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Add table
Reference in a new issue