So, I said I would check in. And that’s happening a little sooner than I thought (I think I just needed a boost and to share something again).
Anyway, the structure of all our RPCs are done. The thing to remember is that each other these RPCs (except for the Signup
and Signin
RPCs) is
sent with a JWT that includes that User’s public signature. Every payload that is sent is signed with the private signature and then paired with a token,
which required all of its credentials to generate the token and get the verifiable public signing key.
Anyway, I think that’s a very good thing but security experts may disagree. Pretty cool to not have to hit the DB to verify the signature with that much authority… but I digress.
So, again we’re just talking “protocol” right now. We’re talking about the happy path, but we’re not forgetting to hold space for the malicious user and developers. We know, that any time you create a platform where someone can deliver code to someone else that runs on their machine, there is vulnerability, and currently (and I don’t think this is going to change for a very long time) we don’t have programs that can perfectly insulate us from those who “just want to watch the world burn.”
So, that means, (as with every functioning system) we know that there will need to be some form of regulatory system / body which monitors the actual code that is being written by developers, and ideally, that system would be very open, but we also understand that some enjoy competition and I think there are those of us that can hold space for a “market socialism” as long as the “market” part is fun and everybody’s needs are met. I do think that the “market” component should always be opt out / opt in. It’s more fun and productive when the people who are playing, want to play. Anyway, we gotta have a review system where we make sure nobody is “man-in-the-middle”ing the credentials as they’re entered in forms to log in (if that’s even necessary. we may be able to just redirect for the one special login app that has a special build). Anyway, just want to protect against people being jerks. (Maybe we should just force everybody to OSS their shit? I don’t know how much they’d love that. We could do something instead where they like just make the source code visible to everyone who ask… oh I think that’s GNU or whichever one Tesla keeps violating).
Anyway, lotta tangents…
So the operations that seem necessary to me, right now, with the names - while being long - of the functions and their parameters, communicate with a level of accuracy and clarity, that it warrants the long name. And also, currently I am the only “dev” to build this “ex” for and I think it’s sick. But again, while I understand that A commitment needs to be made, the robustness of a solution is determined by its ability to evolve to meet the needs of its users OVER TIME.
Okay, so with all that being said about this one .proto
file, this is generally what I’m thinking:
// BIG EDIT:
// I left out all my comments last time and
// then I just made some changes a few min ago
// that I think are important. History is in git
// but this is the better version.
syntax = "proto3";
package app;
service Application {
rpc Signup(SignupParams) returns (Token) {}
rpc Signin(SigninParams) returns (User) {}
// decrypts user with a token that was snapped from the current device and used
// to encrypt the user's data
rpc GenerateToken(GenerateTokenParams) returns (Token) {}
rpc UpdateSelf(UpdateSelfParams) returns (User) {}
rpc DeactivateSelf(DeactivateSelfParams) returns (Empty) {}
rpc GetPods(GetPodsParams) returns (Pods) {}
rpc CreatePod(CreatePodParams) returns (Pod) {}
rpc UpdatePod(UpdatePodParams) returns (Pod) {}
rpc DeletePod(DeletePodParams) returns (Empty) {}
rpc AdmitUserToPod(AdmitUserToPodParams) returns (User) {}
rpc KickUserFromPod(KickUserFromPodParams) returns (User) {}
rpc RequestPodAccess(RequestPodAccessParams) returns (Empty) {}
rpc GetMessagesFromPod(GetMessagesFromPodParams) returns (Messages) {}
rpc PutMessageToPods(PutMessageToPodsParams) returns (Message) {}
rpc DeleteMessages(DeleteMessagesParams) returns (Messages) {}
}
message Empty {}
// AUTH
message Token {
string token = 1;
}
message SignupParams {
string username = 1; // username has to be signed
string password = 2;
bytes pub_signing_key = 3;
// used for shared secret creation
// and is cycled regularly to ensure
// "batched" forward secrecy (if a user is
// offline, then they aren't cycling their
// key for a given device).
// each device gets its own unique_pub_key.
// this would also mean that each web browser
// would get its own unique_pub_key
bytes initial_pub_device_exchange_key = 4; // every pub key is 256 bits so split on 256 (which i think is 32 for a u8 arr?)
}
// we get the user back from signing in but no token
// we do have the ability, however, to decrypt (with one of our public keys)
// the content in our user object that has been returned. from that user
// object, we get the pod_ids that we care about and then we send the
// `GenerateToken` request with those pod_ids.
message SigninParams {
string username = 1; // signed
string password = 2;
bytes username_signature = 3;
}
// Uses pod_ids from the SigninParams user
message GenerateTokenParams {
string username = 1; // signed
string password = 2;
repeated string pod_ids = 3; // signed
bytes signatures = 4;
}
// USER
message Users {
repeated User users = 1;
}
message User {
string username = 1;
bytes pub_signing_key = 2;
bytes pub_exchange_keys = 3;
bytes key_set = 4;
bytes nonce = 5;
bytes encrypted_content = 9;
}
message UpdateSelfParams {
bytes pub_exchange_keys = 1;
bytes key_sets = 2;
bytes content_nonce = 3;
bytes encrypted_content = 4;
}
message DeactivateSelfParams {
string password = 2;
// TODO: we need to start just including the OTP
// TODO: people are going to learn how to handle
// TODO: online secrecy once they understand just
// TODO: how terrible the current system is for their
// TODO: health and wellbeing.
}
// POD
message Pods {
repeated Pod pods = 1;
}
// each pod has an "owner" and "admins" that was the OG creator
// and appointed helpers. the "owners" accounts are delegated the responsibility
// of admission, rejection, kicking, and publishing
// updates to the pod data and can delegate the responsibility
// of publishing to the admins by auto approving when the request
// hits the owner's device. this prevents duplicate writes from
// multiple devices.
message Pod {
string id = 1;
bytes nonce = 2;
bytes key_set = 3;
bytes encrypted_content = 4;
repeated Message messages = 5; // latest (n) records
}
message GetPodsParams {
repeated string ids = 1;
}
message CreatePodParams {
bytes content_nonce = 1;
bytes encrypted_content = 2;
}
message UpdatePodParams {
string id = 1;
bytes content_nonce = 2;
bytes encrypted_content = 3;
bytes content_consumer_pairs = 4;
}
message DeletePodParams {
string id = 1;
}
// USER <-> POD
message AdmitUserToPodParams {
string pod_id = 1; // compare against JWT pod ids
string username = 2; // signed - compare against user sig in the db
}
message KickUserFromPodParams {
string pod_id = 1; // compare against JWT pod ids
string username = 2; // signed - compare against user sig in the db
}
// pod "slot" purchasing happens through stripe's system
// and ends up on our user record. it is updated in our system
// when stripe updates the number of subscriptions. the actual
// pods that they're in are unknown to us.
message RequestPodAccessParams {
string pod_id = 1; // signed
}
// MESSAGE
// your messages from old pods are always accessible to you
// until you delete them. you can always comb your groups
// to get your old messages. we could also encrypt them under
// their account and denormalize...
message Messages {
repeated Message messages = 1;
}
message Message {
string id = 1;
bytes nonce = 2;
bytes key_set = 3;
bytes encrypted_content = 4;
}
message GetMessagesFromPodParams {
string pod_id = 1;
uint32 max_count = 2;
// the `since` and `pub_exchange_keys` are correlated, in that
// the `since` should also have been used to grab the range of
// `pub_exchange_keys` held in the indexeddb table
uint64 since = 3; // unix timestamp
repeated bytes pub_exchange_keys = 4; // for specifying range of public exchange keys
}
message PutMessageToPodsParams {
repeated string pod_ids = 1; // signed and compared against JWT pod ids
bytes key_sets = 2;
bytes nonce = 3;
bytes encrypted_content = 4;
}
message DeleteMessagesParams {
repeated string ids = 1; // signed and compared against JWT
}
Conclusion
Maybe the Signup
and Signin
will just be in a special build of the client
that isn’t distributed, and we can do an easier time of ensuring that any clients
which are built for the system and want to be vendored through the central app store pod
that I own and create (I think pods have to have owners so that there can be one central
moderator who has the primitives to delegate responsibilities and hold the group accountable
is necessary for a system that is inherently un-moderate-able without access to the encrypted content).
Anyway, while we can’t close off the endpoint that needs to be exposed to the public internet, we can corral A threat vector, and simplify for the major % of the group that is either only making a mistake, or is doing the right thing without trying. I think while the system will depend on its own infrastructure (which I really think is cool for things like the “application client gallery”) we do need to make sure that the system sets itself up for future success by creating safety, first and foremost for its users and the people who want to share their “best and brightest” content with the world, without fear. I think ownership and delegation of responsibility for enforcement of the code of conduct a group decides on and allows to evolve over time, is the only way to give people the safety in each of these pods.
People should be able to vote if they want. At the end of the day, the owner makes the final decision with its signature and its key from its device, but we can write clients such the they automatically load those messages up and then they send them all if they were from approved senders.
Anything is possible with the clients we can build, and everyone can be as safe as they choose to allow themselves to be online.
Damn… more tangents. Anyway, those are all the RPCs we need to hit this backend shit from the outside world, so now it’s just about gluing all the cryptography bullshit to the protocol bullshit, and on-device storage bullshit, and then finishing writing the backend bullshit, and then finishing the app client PWA… ugh. Lots to do.
Getting back to gluing all the client parts together (distributed logic and punching the cool shit out to the furthest “edges” of “the edge” has always fascinated me even when some of my thoughts have seemed absurd, so it keeps me engaged better), and then probably blending in some backend work too… I’m almost done with the on-device storage stuff and I really enjoy delivering the backend app + associated client logic, together, because it just feels so much more organized and more wholesome / complete.