mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 08:41:09 +00:00
feat(types): add trait SignExt
for easy chaining
This commit is contained in:
parent
8876480732
commit
8f20aa0cf2
3 changed files with 72 additions and 66 deletions
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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"))?)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue