feat(types): add trait SignExt for easy chaining

This commit is contained in:
oxalica 2024-09-21 13:57:27 -04:00
parent 8876480732
commit 8f20aa0cf2
3 changed files with 72 additions and 66 deletions

View file

@ -99,6 +99,36 @@ pub struct Signee<T> {
pub user: UserKey, 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<Signed<Self>, SignatureError>;
fn sign_msg(
self,
id_key: &PubKey,
act_key: &SigningKey,
) -> Result<Signed<Self>, SignatureError> {
self.sign_msg_with(id_key, act_key, get_timestamp(), &mut rand::thread_rng())
}
}
impl<T: Serialize> SignExt for T {
fn sign_msg_with(
self,
id_key: &PubKey,
act_key: &SigningKey,
timestamp: u64,
rng: &mut (impl RngCore + ?Sized),
) -> Result<Signed<Self>, SignatureError> {
Signed::new(id_key, act_key, timestamp, rng, self)
}
}
pub fn get_timestamp() -> u64 { pub fn get_timestamp() -> u64 {
SystemTime::now() SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
@ -113,7 +143,9 @@ impl<T: Serialize> Signed<T> {
} }
/// Sign the payload with the given `key`. /// 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, id_key: &PubKey,
act_key: &SigningKey, act_key: &SigningKey,
timestamp: u64, timestamp: u64,
@ -129,8 +161,9 @@ impl<T: Serialize> Signed<T> {
id_key: id_key.clone(), id_key: id_key.clone(),
}, },
}; };
let canonical_signee = serde_jcs::to_vec(&signee).expect("serialization cannot fail"); let canonical_signee = serde_jcs::to_vec(&signee).map_err(|_| SignatureError::new())?;
let sig = act_key.try_sign(&canonical_signee)?.to_bytes(); let sig = act_key.sign(&canonical_signee).to_bytes();
Ok(Self { sig, signee }) Ok(Self { sig, signee })
} }
@ -592,15 +625,15 @@ mod tests {
let id_key = SigningKey::from_bytes(&[0x42; 32]); let id_key = SigningKey::from_bytes(&[0x42; 32]);
let act_key = SigningKey::from_bytes(&[0x43; 32]); let act_key = SigningKey::from_bytes(&[0x43; 32]);
let timestamp = 0xDEAD_BEEF; 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()), &PubKey(id_key.verifying_key().to_bytes()),
&act_key, &act_key,
timestamp, timestamp,
&mut fake_rng, &mut fake_rng,
ChatPayload {
rich_text: RichText::from("hello"),
room: Id(42),
},
) )
.unwrap(); .unwrap();

View file

@ -6,13 +6,12 @@ use anyhow::{ensure, Context, Result};
use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile}; use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile};
use blah_types::{ use blah_types::{
bitflags, get_timestamp, ChatPayload, CreateGroup, CreateRoomPayload, Id, PubKey, RichText, 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::spki::der::pem::LineEnding;
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey}; use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey};
use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH}; use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH};
use humantime::Duration; use humantime::Duration;
use rand::rngs::OsRng;
use rand::thread_rng; use rand::thread_rng;
use reqwest::Url; use reqwest::Url;
use rusqlite::{named_params, Connection}; use rusqlite::{named_params, Connection};
@ -336,22 +335,22 @@ fn main_id(cmd: IdCommand) -> Result<()> {
id_key_file, id_key_file,
id_url, id_url,
} => { } => {
let rng = &mut thread_rng(); let id_key_priv = SigningKey::generate(&mut thread_rng());
let id_key_priv = SigningKey::generate(rng);
let id_key = PubKey(id_key_priv.verifying_key().to_bytes()); let id_key = PubKey(id_key_priv.verifying_key().to_bytes());
let act_key_desc = UserActKeyDesc { let act_key_desc = UserActKeyDesc {
act_key: id_key.clone(), act_key: id_key.clone(),
expire_time: i64::MAX as _, expire_time: i64::MAX as _,
comment: "id_key".into(), comment: "id_key".into(),
}; }
let act_key_desc = .sign_msg(&id_key, &id_key_priv)
Signed::sign(&id_key, &id_key_priv, get_timestamp(), rng, act_key_desc)?; .expect("serialization cannot fail");
let profile = UserProfile { let profile = UserProfile {
preferred_chat_server_urls: Vec::new(), preferred_chat_server_urls: Vec::new(),
id_urls: vec![id_url], 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 { let id_desc = UserIdentityDesc {
id_key, id_key,
act_keys: vec![act_key_desc], act_keys: vec![act_key_desc],
@ -399,13 +398,9 @@ fn main_id(cmd: IdCommand) -> Result<()> {
} else if !preferred_chat_server_urls.is_empty() { } else if !preferred_chat_server_urls.is_empty() {
profile.preferred_chat_server_urls = preferred_chat_server_urls; profile.preferred_chat_server_urls = preferred_chat_server_urls;
} }
id_desc.profile = Signed::sign( id_desc.profile = profile
&id_key, .sign_msg(&id_key, &id_key_priv)
&id_key_priv, .expect("serialization cannot fail");
get_timestamp(),
&mut thread_rng(),
profile,
)?;
let id_desc_str = serde_json::to_string_pretty(&id_desc).unwrap(); 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")?; 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")?; .context("invalid expire time")?;
let rng = &mut thread_rng();
let act_key_desc = UserActKeyDesc { let act_key_desc = UserActKeyDesc {
act_key, act_key,
expire_time: expire_time as _, expire_time: expire_time as _,
comment: comment.unwrap_or_default(), comment: comment.unwrap_or_default(),
}; }
let act_key_desc = .sign_msg(&id_key, &id_key_priv)
Signed::sign(&id_key, &id_key_priv, get_timestamp(), rng, act_key_desc)?; .expect("serialization cannot fail");
id_desc.act_keys.push(act_key_desc); id_desc.act_keys.push(act_key_desc);
let id_desc_str = serde_json::to_string_pretty(&id_desc).unwrap(); 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 { let payload = CreateRoomPayload::Group(CreateGroup {
attrs: attrs.unwrap_or_default(), attrs: attrs.unwrap_or_default(),
title, title,
}); })
// FIXME: Same key. // FIXME: Same key.
let payload = Signed::sign( .sign_msg(&PubKey(key.to_bytes()), &key)
&PubKey(key.to_bytes()), .expect("serialization cannot fail");
&key,
get_timestamp(),
&mut OsRng,
payload,
)?;
let ret = client let ret = client
.post(api_url.join("/room/create")?) .post(api_url.join("/room/create")?)
@ -527,15 +516,10 @@ async fn main_api(api_url: Url, command: ApiCommand) -> Result<()> {
let payload = ChatPayload { let payload = ChatPayload {
room: Id(room), room: Id(room),
rich_text: RichText::from(text), rich_text: RichText::from(text),
}; }
// FIXME: Same key. // FIXME: Same key.
let payload = Signed::sign( .sign_msg(&PubKey(key.to_bytes()), &key)
&PubKey(key.to_bytes()), .expect("serialization cannot fail");
&key,
get_timestamp(),
&mut OsRng,
payload,
)?;
let ret = client let ret = client
.post(api_url.join(&format!("/room/{room}/msg"))?) .post(api_url.join(&format!("/room/{room}/msg"))?)

View file

@ -13,7 +13,7 @@ use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile}
use blah_types::{ use blah_types::{
get_timestamp, AuthPayload, ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id, get_timestamp, AuthPayload, ChatPayload, CreateGroup, CreatePeerChat, CreateRoomPayload, Id,
MemberPermission, PubKey, RichText, RoomAdminOp, RoomAdminPayload, RoomAttrs, RoomMetadata, 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, X_BLAH_DIFFICULTY, X_BLAH_NONCE,
}; };
use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs}; use blahd::{ApiError, AppState, Database, RoomList, RoomMsgs};
@ -188,12 +188,11 @@ impl Server {
} }
fn sign<T: Serialize>(&self, user: &User, msg: T) -> Signed<T> { fn sign<T: Serialize>(&self, user: &User, msg: T) -> Signed<T> {
Signed::sign( msg.sign_msg_with(
&user.pubkeys.id_key, &user.pubkeys.id_key,
&user.act_priv, &user.act_priv,
get_timestamp(), get_timestamp(),
&mut *self.rng.borrow_mut(), &mut *self.rng.borrow_mut(),
msg,
) )
.unwrap() .unwrap()
} }
@ -348,13 +347,8 @@ async fn smoke(server: Server) {
} }
fn auth(user: &User, rng: &mut impl RngCore) -> String { fn auth(user: &User, rng: &mut impl RngCore) -> String {
let msg = Signed::sign( let msg = AuthPayload {}
&user.pubkeys.id_key, .sign_msg_with(&user.pubkeys.id_key, &user.act_priv, get_timestamp(), rng)
&user.act_priv,
get_timestamp(),
rng,
AuthPayload {},
)
.unwrap(); .unwrap();
serde_json::to_string(&msg).unwrap() serde_json::to_string(&msg).unwrap()
} }
@ -980,17 +974,12 @@ async fn register(server: Server) {
}; };
let mut id_desc = { let mut id_desc = {
// Sign using id_key. // Sign using id_key.
let act_key = Signed::sign( let act_key = UserActKeyDesc {
&CAROL.pubkeys.id_key,
&CAROL.id_priv,
get_timestamp(),
&mut *server.rng(),
UserActKeyDesc {
act_key: CAROL.pubkeys.act_key.clone(), act_key: CAROL.pubkeys.act_key.clone(),
expire_time: u64::MAX, expire_time: u64::MAX,
comment: "comment".into(), comment: "comment".into(),
}, }
) .sign_msg(&CAROL.pubkeys.id_key, &CAROL.id_priv)
.unwrap(); .unwrap();
let profile = sign_profile("https://localhost".parse().unwrap()); let profile = sign_profile("https://localhost".parse().unwrap());
UserIdentityDesc { UserIdentityDesc {