mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 08:41:09 +00:00
Enforce sorted fields for signed payloads
This commit is contained in:
parent
74bd0d42e2
commit
cf5d648315
3 changed files with 45 additions and 6 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -239,6 +239,7 @@ dependencies = [
|
||||||
"serde-aux",
|
"serde-aux",
|
||||||
"serde-constant",
|
"serde-constant",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"syn",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|
|
@ -28,5 +28,8 @@ tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
syn = { version = "2.0.76", features = ["full", "visit"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [ "./blahctl" ]
|
members = [ "./blahctl" ]
|
||||||
|
|
47
src/types.rs
47
src/types.rs
|
@ -1,5 +1,8 @@
|
||||||
|
//! NB. All structs here that are part of signee must be lexically sorted, as RFC8785.
|
||||||
|
//! This is tested by `canonical_fields_sorted`.
|
||||||
|
//! See: https://www.rfc-editor.org/rfc/rfc8785
|
||||||
|
//! FIXME: `typ` is still always the first field because of `serde`'s implementation.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
// NB. All structs here that are part of signee must be lexically sorted, as RFC8785.
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use anyhow::{ensure, Context};
|
use anyhow::{ensure, Context};
|
||||||
|
@ -29,7 +32,6 @@ impl fmt::Display for UserKey {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct WithSig<T> {
|
pub struct WithSig<T> {
|
||||||
// sorted
|
|
||||||
#[serde(with = "hex::serde")]
|
#[serde(with = "hex::serde")]
|
||||||
pub sig: [u8; SIGNATURE_LENGTH],
|
pub sig: [u8; SIGNATURE_LENGTH],
|
||||||
pub signee: Signee<T>,
|
pub signee: Signee<T>,
|
||||||
|
@ -38,7 +40,6 @@ pub struct WithSig<T> {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Signee<T> {
|
pub struct Signee<T> {
|
||||||
// sorted
|
|
||||||
pub nonce: u32,
|
pub nonce: u32,
|
||||||
pub payload: T,
|
pub payload: T,
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
@ -82,7 +83,6 @@ impl<T: Serialize> WithSig<T> {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "typ", rename = "chat")]
|
#[serde(tag = "typ", rename = "chat")]
|
||||||
pub struct ChatPayload {
|
pub struct ChatPayload {
|
||||||
// sorted
|
|
||||||
pub room: Uuid,
|
pub room: Uuid,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
@ -92,8 +92,8 @@ pub type ChatItem = WithSig<ChatPayload>;
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "typ", rename = "create_room")]
|
#[serde(tag = "typ", rename = "create_room")]
|
||||||
pub struct CreateRoomPayload {
|
pub struct CreateRoomPayload {
|
||||||
pub title: String,
|
|
||||||
pub attrs: RoomAttrs,
|
pub attrs: RoomAttrs,
|
||||||
|
pub title: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Proof of room membership for read-access.
|
/// Proof of room membership for read-access.
|
||||||
|
@ -107,7 +107,6 @@ pub struct AuthPayload {}
|
||||||
#[serde(deny_unknown_fields, tag = "typ", rename_all = "snake_case")]
|
#[serde(deny_unknown_fields, tag = "typ", rename_all = "snake_case")]
|
||||||
pub enum RoomAdminPayload {
|
pub enum RoomAdminPayload {
|
||||||
AddMember {
|
AddMember {
|
||||||
// sorted
|
|
||||||
permission: RoomPermission,
|
permission: RoomPermission,
|
||||||
room: Uuid,
|
room: Uuid,
|
||||||
user: UserKey,
|
user: UserKey,
|
||||||
|
@ -187,3 +186,39 @@ mod sql_impl {
|
||||||
|
|
||||||
impl_u64_flag!(ServerPermission, RoomPermission, RoomAttrs);
|
impl_u64_flag!(ServerPermission, RoomPermission, RoomAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Visitor {
|
||||||
|
errors: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> syn::visit::Visit<'ast> for Visitor {
|
||||||
|
fn visit_fields_named(&mut self, i: &'ast syn::FieldsNamed) {
|
||||||
|
let fields = i
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.flat_map(|f| f.ident.clone())
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !fields.windows(2).all(|w| w[0] < w[1]) {
|
||||||
|
writeln!(self.errors, "unsorted fields: {fields:?}").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_fields_sorted() {
|
||||||
|
let src = std::fs::read_to_string(file!()).unwrap();
|
||||||
|
let file = syn::parse_file(&src).unwrap();
|
||||||
|
|
||||||
|
let mut v = Visitor::default();
|
||||||
|
syn::visit::visit_file(&mut v, &file);
|
||||||
|
if !v.errors.is_empty() {
|
||||||
|
panic!("{}", v.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue