diff --git a/blah-types/src/lib.rs b/blah-types/src/lib.rs index 4d3b5db..8cebb36 100644 --- a/blah-types/src/lib.rs +++ b/blah-types/src/lib.rs @@ -99,6 +99,36 @@ pub struct Signee { pub user: UserKey, } +pub trait SignExt: Sized { + fn sign_msg_with( + self, + id_key: &PubKey, + act_key: &SigningKey, + timestamp: u64, + rng: &mut (impl RngCore + ?Sized), + ) -> Result, SignatureError>; + + fn sign_msg( + self, + id_key: &PubKey, + act_key: &SigningKey, + ) -> Result, SignatureError> { + self.sign_msg_with(id_key, act_key, get_timestamp(), &mut rand::thread_rng()) + } +} + +impl SignExt for T { + fn sign_msg_with( + self, + id_key: &PubKey, + act_key: &SigningKey, + timestamp: u64, + rng: &mut (impl RngCore + ?Sized), + ) -> Result, SignatureError> { + Signed::new(id_key, act_key, timestamp, rng, self) + } +} + pub fn get_timestamp() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -113,7 +143,9 @@ impl Signed { } /// Sign the payload with the given `key`. - pub fn sign( + /// + /// This operation only fail when serialization of `payload` fails. + pub fn new( id_key: &PubKey, act_key: &SigningKey, timestamp: u64, @@ -129,8 +161,9 @@ impl Signed { id_key: id_key.clone(), }, }; - let canonical_signee = serde_jcs::to_vec(&signee).expect("serialization cannot fail"); - let sig = act_key.try_sign(&canonical_signee)?.to_bytes(); + let canonical_signee = serde_jcs::to_vec(&signee).map_err(|_| SignatureError::new())?; + let sig = act_key.sign(&canonical_signee).to_bytes(); + Ok(Self { sig, signee }) } @@ -592,15 +625,15 @@ mod tests { let id_key = SigningKey::from_bytes(&[0x42; 32]); let act_key = SigningKey::from_bytes(&[0x43; 32]); let timestamp = 0xDEAD_BEEF; - let msg = Signed::sign( + let msg = ChatPayload { + rich_text: RichText::from("hello"), + room: Id(42), + } + .sign_msg_with( &PubKey(id_key.verifying_key().to_bytes()), &act_key, timestamp, &mut fake_rng, - ChatPayload { - rich_text: RichText::from("hello"), - room: Id(42), - }, ) .unwrap(); diff --git a/blahctl/src/main.rs b/blahctl/src/main.rs index 276da20..3f3bea0 100644 --- a/blahctl/src/main.rs +++ b/blahctl/src/main.rs @@ -6,13 +6,12 @@ use anyhow::{ensure, Context, Result}; use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile}; use blah_types::{ bitflags, get_timestamp, ChatPayload, CreateGroup, CreateRoomPayload, Id, PubKey, RichText, - RoomAttrs, ServerPermission, Signed, + RoomAttrs, ServerPermission, SignExt, }; use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey}; use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH}; use humantime::Duration; -use rand::rngs::OsRng; use rand::thread_rng; use reqwest::Url; use rusqlite::{named_params, Connection}; @@ -336,22 +335,22 @@ fn main_id(cmd: IdCommand) -> Result<()> { id_key_file, id_url, } => { - let rng = &mut thread_rng(); - let id_key_priv = SigningKey::generate(rng); + let id_key_priv = SigningKey::generate(&mut thread_rng()); let id_key = PubKey(id_key_priv.verifying_key().to_bytes()); let act_key_desc = UserActKeyDesc { act_key: id_key.clone(), expire_time: i64::MAX as _, comment: "id_key".into(), - }; - let act_key_desc = - Signed::sign(&id_key, &id_key_priv, get_timestamp(), rng, act_key_desc)?; + } + .sign_msg(&id_key, &id_key_priv) + .expect("serialization cannot fail"); let profile = UserProfile { preferred_chat_server_urls: Vec::new(), id_urls: vec![id_url], - }; - let profile = Signed::sign(&id_key, &id_key_priv, get_timestamp(), rng, profile)?; + } + .sign_msg(&id_key, &id_key_priv) + .expect("serialization cannot fail"); let id_desc = UserIdentityDesc { id_key, act_keys: vec![act_key_desc], @@ -399,13 +398,9 @@ fn main_id(cmd: IdCommand) -> Result<()> { } else if !preferred_chat_server_urls.is_empty() { profile.preferred_chat_server_urls = preferred_chat_server_urls; } - id_desc.profile = Signed::sign( - &id_key, - &id_key_priv, - get_timestamp(), - &mut thread_rng(), - profile, - )?; + id_desc.profile = profile + .sign_msg(&id_key, &id_key_priv) + .expect("serialization cannot fail"); let id_desc_str = serde_json::to_string_pretty(&id_desc).unwrap(); fs::write(desc_file, &id_desc_str).context("failed to save identity description")?; @@ -440,14 +435,13 @@ fn main_id(cmd: IdCommand) -> Result<()> { }) .context("invalid expire time")?; - let rng = &mut thread_rng(); let act_key_desc = UserActKeyDesc { act_key, expire_time: expire_time as _, comment: comment.unwrap_or_default(), - }; - let act_key_desc = - Signed::sign(&id_key, &id_key_priv, get_timestamp(), rng, act_key_desc)?; + } + .sign_msg(&id_key, &id_key_priv) + .expect("serialization cannot fail"); id_desc.act_keys.push(act_key_desc); let id_desc_str = serde_json::to_string_pretty(&id_desc).unwrap(); @@ -498,15 +492,10 @@ async fn main_api(api_url: Url, command: ApiCommand) -> Result<()> { let payload = CreateRoomPayload::Group(CreateGroup { attrs: attrs.unwrap_or_default(), title, - }); + }) // FIXME: Same key. - let payload = Signed::sign( - &PubKey(key.to_bytes()), - &key, - get_timestamp(), - &mut OsRng, - payload, - )?; + .sign_msg(&PubKey(key.to_bytes()), &key) + .expect("serialization cannot fail"); let ret = client .post(api_url.join("/room/create")?) @@ -527,15 +516,10 @@ async fn main_api(api_url: Url, command: ApiCommand) -> Result<()> { let payload = ChatPayload { room: Id(room), rich_text: RichText::from(text), - }; + } // FIXME: Same key. - let payload = Signed::sign( - &PubKey(key.to_bytes()), - &key, - get_timestamp(), - &mut OsRng, - payload, - )?; + .sign_msg(&PubKey(key.to_bytes()), &key) + .expect("serialization cannot fail"); let ret = client .post(api_url.join(&format!("/room/{room}/msg"))?) diff --git a/blahd/tests/webapi.rs b/blahd/tests/webapi.rs index 3fefd8f..017a68f 100644 --- a/blahd/tests/webapi.rs +++ b/blahd/tests/webapi.rs @@ -13,7 +13,7 @@ use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile} use blah_types::{ get_timestamp, AuthPayload, ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id, MemberPermission, PubKey, RichText, RoomAdminOp, RoomAdminPayload, RoomAttrs, RoomMetadata, - ServerPermission, Signed, SignedChatMsg, UserKey, UserRegisterPayload, WithMsgId, + ServerPermission, SignExt, Signed, SignedChatMsg, UserKey, UserRegisterPayload, WithMsgId, X_BLAH_DIFFICULTY, X_BLAH_NONCE, }; use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs}; @@ -188,12 +188,11 @@ impl Server { } fn sign(&self, user: &User, msg: T) -> Signed { - Signed::sign( + msg.sign_msg_with( &user.pubkeys.id_key, &user.act_priv, get_timestamp(), &mut *self.rng.borrow_mut(), - msg, ) .unwrap() } @@ -348,14 +347,9 @@ async fn smoke(server: Server) { } fn auth(user: &User, rng: &mut impl RngCore) -> String { - let msg = Signed::sign( - &user.pubkeys.id_key, - &user.act_priv, - get_timestamp(), - rng, - AuthPayload {}, - ) - .unwrap(); + let msg = AuthPayload {} + .sign_msg_with(&user.pubkeys.id_key, &user.act_priv, get_timestamp(), rng) + .unwrap(); serde_json::to_string(&msg).unwrap() } @@ -980,17 +974,12 @@ async fn register(server: Server) { }; let mut id_desc = { // Sign using id_key. - let act_key = Signed::sign( - &CAROL.pubkeys.id_key, - &CAROL.id_priv, - get_timestamp(), - &mut *server.rng(), - UserActKeyDesc { - act_key: CAROL.pubkeys.act_key.clone(), - expire_time: u64::MAX, - comment: "comment".into(), - }, - ) + let act_key = UserActKeyDesc { + act_key: CAROL.pubkeys.act_key.clone(), + expire_time: u64::MAX, + comment: "comment".into(), + } + .sign_msg(&CAROL.pubkeys.id_key, &CAROL.id_priv) .unwrap(); let profile = sign_profile("https://localhost".parse().unwrap()); UserIdentityDesc {