mirror of
				https://github.com/Blah-IM/blahrs.git
				synced 2025-10-31 10:41:37 +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-constant", | ||||
|  "serde_json", | ||||
|  "syn", | ||||
|  "tokio", | ||||
|  "tokio-stream", | ||||
|  "tower-http", | ||||
|  |  | |||
|  | @ -28,5 +28,8 @@ tracing = "0.1.40" | |||
| tracing-subscriber = "0.3.18" | ||||
| uuid = { version = "1.10.0", features = ["serde", "v4"] } | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| syn = { version = "2.0.76", features = ["full", "visit"] } | ||||
| 
 | ||||
| [workspace] | ||||
| 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; | ||||
| // NB. All structs here that are part of signee must be lexically sorted, as RFC8785.
 | ||||
| use std::time::SystemTime; | ||||
| 
 | ||||
| use anyhow::{ensure, Context}; | ||||
|  | @ -29,7 +32,6 @@ impl fmt::Display for UserKey { | |||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| pub struct WithSig<T> { | ||||
|     // sorted
 | ||||
|     #[serde(with = "hex::serde")] | ||||
|     pub sig: [u8; SIGNATURE_LENGTH], | ||||
|     pub signee: Signee<T>, | ||||
|  | @ -38,7 +40,6 @@ pub struct WithSig<T> { | |||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| #[serde(deny_unknown_fields)] | ||||
| pub struct Signee<T> { | ||||
|     // sorted
 | ||||
|     pub nonce: u32, | ||||
|     pub payload: T, | ||||
|     pub timestamp: u64, | ||||
|  | @ -82,7 +83,6 @@ impl<T: Serialize> WithSig<T> { | |||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| #[serde(tag = "typ", rename = "chat")] | ||||
| pub struct ChatPayload { | ||||
|     // sorted
 | ||||
|     pub room: Uuid, | ||||
|     pub text: String, | ||||
| } | ||||
|  | @ -92,8 +92,8 @@ pub type ChatItem = WithSig<ChatPayload>; | |||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| #[serde(tag = "typ", rename = "create_room")] | ||||
| pub struct CreateRoomPayload { | ||||
|     pub title: String, | ||||
|     pub attrs: RoomAttrs, | ||||
|     pub title: String, | ||||
| } | ||||
| 
 | ||||
| /// Proof of room membership for read-access.
 | ||||
|  | @ -107,7 +107,6 @@ pub struct AuthPayload {} | |||
| #[serde(deny_unknown_fields, tag = "typ", rename_all = "snake_case")] | ||||
| pub enum RoomAdminPayload { | ||||
|     AddMember { | ||||
|         // sorted
 | ||||
|         permission: RoomPermission, | ||||
|         room: Uuid, | ||||
|         user: UserKey, | ||||
|  | @ -187,3 +186,39 @@ mod sql_impl { | |||
| 
 | ||||
|     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
		Add a link
		
	
		Reference in a new issue
	
	 oxalica
						oxalica