Got us to the point where all but on-device public key routing tables are set up.
Basically what’s left now is handling special categories of messages in “personal pods” that are for initial key exchange (all future key exchange will be private after the first check-in) which are encrypted with a singular “publicly advertised” public key that a user makes available to receive encrypted requests for actually useful key exchange (this “ultra-public” exchange key is cycled frequently and only used to establish the initial key sets which will be used for all future exchanges).
This is for the client…
rpcs/auth.rs
use crate::{app::InnerUser, store::SIGNING_KEY_ADDRESS};
use crypto::{encrypt_and_sign_content, generate_signing_keys};
use gloo_console::log;
use prost::Message;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn signup(username: String, password: String, user: crate::rpcs::user::User) {
let inner_user = InnerUser {
metadata: user.get_metadata(),
};
let (signing_key, verifying_key) = generate_signing_keys();
crate::store::write_key_pair_encrypted(SIGNING_KEY_ADDRESS, signing_key).unwrap();
// TODO: fill in that vec with your current public key
// TODO: all this will be managed with the key routing table
let public_keys = vec![];
let (nonce, key_sets, encrypted_content, encrypted_content_signature) =
encrypt_and_sign_content(signing_key, inner_user.encode_to_vec(), public_keys).unwrap();
let signup_params = crate::app::SignupParams {
username: username.clone(),
password: password.clone(),
verifying_key: verifying_key.to_vec(),
nonce: nonce.to_vec(),
key_set: key_sets,
encrypted_content,
encrypted_content_signature,
// TODO: worry about saving exchange keys and generating them the same way you
// TODO: do in the database. this will all be managed with the key routing table
initial_exchange_key: vec![],
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
match client.signup(signup_params).await {
Ok(res) => {
let token = res.into_inner().token;
crate::store::set_token(token);
}
Err(err) => log!(err.message().to_string()),
}
});
}
#[wasm_bindgen]
pub fn signin(username: String, password: String, signing_key: String) {
let signing_key = hex::decode(signing_key).unwrap();
let signing_key_as_bytes: &[u8] = &signing_key;
let signing_key_as_fixed_bytes: [u8; 32] = signing_key_as_bytes.try_into().unwrap();
crate::store::write_key_pair_encrypted(SIGNING_KEY_ADDRESS, signing_key_as_fixed_bytes)
.unwrap();
let signin_params = crate::app::SigninParams {
username: username.clone(),
password: password.clone(),
username_signature: crypto::sign_content(
username.as_bytes().to_vec(),
signing_key_as_fixed_bytes,
),
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
match client.signin(signin_params).await {
Ok(res) => {
let token = res.into_inner().token;
crate::store::set_token(token);
}
Err(err) => log!(err.message().to_string()),
}
});
}
rpcs/message.rs
use crate::{
app::InnerMessage,
store::{get_value_key_from_address_key_decrypted, SIGNING_KEY_ADDRESS},
};
use gloo_console::error;
use crypto::sign_content;
use parking_lot::Mutex;
use prost::Message as ProstMessage;
use std::sync::Arc;
use crate::app::PutMessageToPodParams;
use wasm_bindgen::prelude::*;
#[derive(Clone)]
#[wasm_bindgen]
pub struct Message {
metadata: Vec<u8>,
}
#[wasm_bindgen]
impl Message {
#[wasm_bindgen(constructor)]
pub fn new(metadata: Vec<u8>) -> Message {
Message { metadata }
}
}
#[wasm_bindgen]
pub struct MessageVec(Vec<Message>);
#[wasm_bindgen]
pub fn get_messages_from_pod(pod_id: String, max_count: u32, start: u64, end: u64) -> MessageVec {
// TODO: go get public keys from local cache that has the public key
// TODO: for that `start` / `end` date range. this will all be managed
// TODO: by the key routing table stuff
let public_keys = vec![];
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let pod_id_signature = sign_content(pod_id.as_bytes().to_vec(), signing_key);
let get_messages_from_pod_params = crate::app::GetMessagesFromPodParams {
pod_id,
max_count,
start,
end,
public_keys,
pod_id_signature,
};
let messages: Arc<Mutex<Vec<Message>>> = Arc::new(Mutex::new(Vec::new()));
let messages_clone = Arc::clone(&messages);
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
match client
.get_messages_from_pod(get_messages_from_pod_params)
.await
{
Ok(res) => {
let mut translated_messages: Vec<Message> = vec![];
for message in res.into_inner().messages {
let mut receiver_pub_exchange_key = [0u8; 32];
let mut sender_pub_exchange_key = [0u8; 32];
let mut encrypted_content_key = [0u8; 32];
for (i, key) in message.key_set.chunks(32).enumerate() {
let key: [u8; 32] = key
.try_into()
.expect("unable to convert key to fixed bytes");
match i {
0 => receiver_pub_exchange_key = key,
1 => sender_pub_exchange_key = key,
2 => encrypted_content_key = key,
_ => (),
}
}
let receiver_priv_exchange_key =
get_value_key_from_address_key_decrypted(receiver_pub_exchange_key);
let nonce_as_bytes: &[u8] = &message.nonce;
let nonce_as_fixed_bytes: [u8; 24] = nonce_as_bytes
.try_into()
.expect("unable to convert nonce to fixed bytes");
let encoded_content = crypto::decrypt(
sender_pub_exchange_key,
receiver_priv_exchange_key,
encrypted_content_key,
nonce_as_fixed_bytes,
message.encrypted_content,
)
.expect("failed to decrypt content");
let inner_message = InnerMessage::decode(&*encoded_content)
.expect("failed to decode inner message");
translated_messages.push(Message {
metadata: inner_message.metadata,
})
}
let mut messages_lock = messages_clone.lock();
*messages_lock = translated_messages;
}
Err(err) => error!(err.message().to_string()),
}
});
let messages_lock = messages.lock().clone();
MessageVec(messages_lock)
}
#[wasm_bindgen]
pub fn put_message_to_pod(pod_id: String, message: Message) {
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let inner_message = InnerMessage {
metadata: message.metadata,
};
// TODO: swap out the `vec![]` with the public keys for the users for this pod from your routing table
let public_keys = vec![];
let (nonce, key_sets, encrypted_content, encrypted_content_signature) =
crypto::encrypt_and_sign_content(
signing_key,
inner_message.encode_to_vec(),
public_keys,
)
.unwrap();
let put_message_to_pod_params = PutMessageToPodParams {
pod_id,
key_sets,
nonce: nonce.to_vec(),
encrypted_content,
encrypted_content_signature,
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
if let Err(err) = client.put_message_to_pod(put_message_to_pod_params).await {
error!(err.message().to_string());
}
});
}
#[wasm_bindgen]
pub fn delete_message(id: String, pod_id: String, timestamp: u64) {
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let id_signature = crypto::sign_content(id.as_bytes().to_vec(), signing_key);
let delete_message_params = crate::app::DeleteMessageParams {
id,
pod_id,
timestamp,
id_signature,
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
if let Err(err) = client.delete_message(delete_message_params).await {
error!(err.message().to_string());
}
});
}
rpcs/user.rs
use crate::app::{DeleteSelfParams, GetUserParams, InnerUser, UpdateSelfParams};
use crypto::sign_content;
use parking_lot::Mutex;
use prost::Message;
use std::sync::Arc;
use gloo_console::error;
use crate::store::{get_value_key_from_address_key_decrypted, SIGNING_KEY_ADDRESS};
use wasm_bindgen::prelude::*;
#[derive(Clone)]
#[wasm_bindgen]
pub struct User {
metadata: Vec<u8>,
}
#[wasm_bindgen]
impl User {
#[wasm_bindgen(constructor)]
pub fn new(metadata: Vec<u8>) -> User {
User { metadata }
}
pub fn get_metadata(&self) -> Vec<u8> {
self.metadata.clone()
}
}
#[wasm_bindgen]
pub fn get_user(username: String) -> User {
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let username_signature = sign_content(username.as_bytes().to_vec(), signing_key);
let get_user_params = GetUserParams {
username,
// TODO: public keys need to be grabbed from the database for the
// TODO: last few that their user profile could currently be encrypted
// TODO: with. this probably isn't the best strategy but can be iterated
// TODO: on.
public_keys: vec![],
username_signature,
};
let user: Arc<Mutex<User>> = Arc::new(Mutex::new(User { metadata: vec![] }));
let user_clone = Arc::clone(&user);
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
match client.get_user(get_user_params).await {
Ok(res) => {
let user = res.into_inner();
let mut receiver_pub_exchange_key = [0u8; 32];
let mut sender_pub_exchange_key = [0u8; 32];
let mut encrypted_content_key = [0u8; 32];
for (i, key) in user.key_set.chunks(32).enumerate() {
let key: [u8; 32] = key
.try_into()
.expect("unable to convert key to fixed bytes");
match i {
0 => receiver_pub_exchange_key = key,
1 => sender_pub_exchange_key = key,
2 => encrypted_content_key = key,
_ => (),
}
}
let receiver_priv_exchange_key =
get_value_key_from_address_key_decrypted(receiver_pub_exchange_key);
let nonce_as_bytes: &[u8] = &user.nonce;
let nonce_as_fixed_bytes: [u8; 24] = nonce_as_bytes
.try_into()
.expect("unable to convert nonce to fixed bytes");
let encoded_content = crypto::decrypt(
sender_pub_exchange_key,
receiver_priv_exchange_key,
encrypted_content_key,
nonce_as_fixed_bytes,
user.encrypted_content,
)
.expect("failed to decrypt content");
let inner_user =
InnerUser::decode(&*encoded_content).expect("failed to decode inner user");
let mut user_lock = user_clone.lock();
*user_lock = User {
metadata: inner_user.metadata,
};
}
Err(err) => error!(err.message().to_string()),
}
});
let user_lock = user.lock().clone();
user_lock
}
#[wasm_bindgen]
pub fn update_self(user: User) {
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let inner_user = InnerUser {
metadata: user.metadata,
};
// TODO: get the public keys for this pod from the cache table
let public_keys = vec![];
let (nonce, key_sets, encrypted_content, encrypted_content_signature) =
crypto::encrypt_and_sign_content(
signing_key,
inner_user.encode_to_vec(),
public_keys,
)
.unwrap();
let update_self_params = UpdateSelfParams {
key_sets,
nonce: nonce.to_vec(),
encrypted_content,
encrypted_content_signature,
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
if let Err(err) = client.update_self(update_self_params).await {
error!(err.message().to_string());
}
});
}
#[wasm_bindgen]
pub fn delete_self(username: String, password: String) {
let signing_key = get_value_key_from_address_key_decrypted(SIGNING_KEY_ADDRESS);
let username_signature =
crypto::sign_content(username.as_bytes().to_vec(), signing_key);
let delete_self_params = DeleteSelfParams {
username,
password,
username_signature,
};
wasm_bindgen_futures::spawn_local(async move {
let mut client = crate::get_app_client();
if let Err(err) = client.delete_self(delete_self_params).await {
error!(err.message().to_string());
}
});
}
Conclusion
All but the “distributed, public key, routing table, on-device, hell, that isn’t actually as bad as it sounds” is pretty much done on the client side. I was going to offer people the ability to specify their own on-device database drivers, and make it easier for people to build for something other than the browser, but for now I think this is a “composition over inheritance” moment where it would be better to just assemble separate clients that are oriented toward different targets (that may be a heretical application of that term, but basically just don’t want to “extend” a package if ensuring that all consumers have an agnostic experience means creating a whole bunch of other hell. as with everything it’s a trade-off).