7.2 KiB
The Protocol
-
Encryptions used:
- RSA-1024 (can be of somewhat arbitrary length): used for initial server communication, client to client communication and for client authentication.
- SHA3-256 hash: verification of information by signing with the RSA keys (messages and authentication).
- AES-CFB-256:
Used for encryption of communication between the client and the server.
- both PKCS7 and padding-less messages are being sent as most requests are made to be exactly 1 block (128 bits), which technically results in a cookbook style encrytion.
-
Registration is done by getting a public key from the user, and in a second packet getting the 6 digit code, the code is then signed to make sure the key given in the first packet is indeed the expected key. For more details see Registration below
-
Public keys are generated by the client when it wants to register, it is then the clien't job to save private key in a secure fashion (currently it is saved as an unencrypted file in pem format for ease of debug).
The AES session keys are also generated by the client, but they are not saved anywhere as they are only for the current session (if a disconnect happend, a new key needs to be generated). The session key is sent to the server as the first packet, signed with the server's public key, so only the server could obtain them.
The server saves each user public key (and each client can cache other users public keys to reduce network usage), while the keys should be saved in a secure fashion, it is more important for them to not be changed rather then not having general access, as getting someone's public key is as easy as registering and making a simple request.
Passing keys between users is not of a great security risk, as all keys passed are public keys.
-
Messages are first encrypted using the recipient's public key, then signed using the sender's private key (the signature is appended). This allows the recipient to verify the message's claimed origin. Message Acknowledgements are special messages, being sent as normal messages with the same id as the acknowledged message but with the IsAck flag set to true.
-
See Passing messages below
-
See Requests below
-
The server uses 2 Dictionaries as the data structure (see
server/Data.cs
) (not saved to disk due to the non-trivial nature of it), 1 dictionary is used to save the public keys, and the other acts as a mailbox for each client.
About Key Derivation Function
KDF isnt necessary here, as the client already has the server's public key and no key exchange needs to happen, the client can send an encrypted symmetric key to the server, and a secure connection can be established using only 1 request.
NOTE: the server in this case trusts the client to create a good symmetric key. This is not a big issue in my opinion, as the client (if malicious) can send a third party the transfered data regardless.
Initializing a connection
First packet of each connection needs to be an encrypted packet (using the server's public key) of an AES-256 session key appended by some random IV.
Registration:
- User sends a
Register
requset giving them a public key and a phone number. - Server sends the user a
VerificationRequired
message (not in current code, as the 6 digit code does that for now) & a 6-digit code (Secure channel) - User sends the server a
ConfirmRegister
with the 6-digit code, signed using the key provided at previous stage - Server verifies the signature and code, and if both are valid it sends a last
Confirm
and the registration process is done
Login
Login is done by a challenge, the user sends a Login
request, the server sends a random block of 16 bytes for the user to sign,
then the server validates the signature with the known saved key.
Passing messages
In order to send a message from A to B, A will ask the server for B's key,
A will then encrypt the message using B's key, append a signature, and send a SendMessage
request with the payload having the structure of Enc_b(Message object) + Signature_A(Message object)
.
The server will hold on to the message until B will send a GetMessages
request to the server.
NOTE: The server actually doesnt know (and care) how the message is constructed, allowing the change of the message structure without updating the server itself.
Requests
-
Register: data: Phone - 8 bytes, RSA key size (payload length) - 2 bytes
Response: a simple
ValidationRequired
, not implemented as the 6 digit code covers that. -
ConfirmRegister (signed & encrypted 6 digit code) data: 6 bytes for the 6 digit code, 4 bytes for signature length
Response: UTF-8 string of either
OK
orSIG INVALID
, with PKCS7 padding -
Login: data: 8 bytes of user's phone
Response: 16 bytes to be signed, no padding (1 block)
-
ConfirmLogin (signed hash): data: hash length
Response UTF-8 string of either
OK
orINVALID SIG
, PKCS7 padding -
GetMessages: data: EMPTY
Response: 8 shorts, appended with the messages themselves. The last short marks if there are more messages to be read, the rest are the lengths of each corresponding message (by order). No padding as only the 8 shorts are encrypted using the session key.
-
GetUserKey: extra data: 8 bytes (4 bits per digit) of whoever we want to get the key of
Response: The key, PKCS7 padded
-
SendMessage: extra data: 8 bytes (4 bits per digit) of who to send the data, 4 bytes (32bit) for length in bytes
Response: NONE
Request format (see lib/Request.cs:3
- #RequestType
for request type numbers):
{
Version byte (0) - 1 byte,
RequestType - 1 byte,
looping counter - 1 byte,
data - up to 13,
} = 16 bytes = 128 bits
The SendMessage payload structure can be seen in client/Message.cs:55
in
the Bytes()
function.
The binary format contains:
- 1 version byte
- 4 bytes for a message ID
- 8 bytes for the sender's phone number
- 1 byte for flags (IsAck)
- The rest is filled with the message itself
Notes and assumptions
- Print statements are color coded: RED - unencrypted, GREEN - encrypted, ORANGE - General server stuff, WHITE - Messages.
- The given code does not handle errors nicely (connection will simply shutdown and client will crash).
- Stream handling is also not ideal, as both server and client reads all that was sent and not actually read only the amount needed.
- Client does not get messages automatically,
instead a
/get
command (among others such as/pull
and/fetch
) are present in order to get new messages from the server - The client does not handle getting message properly, as not extra reading and no proper buffering is being done (if the incoming messages are too long for the buffer, it will crash).
- The server does not save its current data and does not load from disk. Saving the data and is not as trivial as in python since the RSA class does not contain a constructor.
Part 3
Availability cannot be guaranteed, as the server can always get hit by a rocket, a malicious actor can attempt to scramble or change the packets in transit (which only a reconnect attempt can really solve this, assuming the connection will not be tempered with this time), the client's or server's area might have an extended power/internet outage and a meteor might hit earth and society might collapse.