Protocol
Information about the Holt protocol.
Accounts
Accounts have two authentication factors: a public/private key pair (supported: RSA-OAEP) and a passkey.
The server MUST only store the public key and an encrypted version of the passkey, never receiving the private key.
The public/private key pair MUST be generated by the client, and the passkey by the server.
The server MUST verify the client using the passkey and by requiring the client to decrypt a challenge value encrypted with the public key.
Flow diagram:
Client Server
| |
| Key pair creation |
| /signup ---------------> |
| | Challenge value encrypted
| <------------- Challenge |
| Decryption |
| /challenge ------------> |
| | Verify and passkey creation
| <--- Passkey and session |
| |
Keyfile
On signup the RSA key pair is generated in the browser and offered as a downloadable .keys file. This file holds the private key, which the server never sees, so it is the user’s responsibility to keep it safe.
The passkey (issued by the server) can be handled two ways:
- Combined credentials: the passkey is stored inside the same
.keysfile alongside the private key. Logging in only requires the one file, which is easier but means a single file grants full access. - Separate: the
.keysfile holds only the private key and the passkey is kept on its own (for example saved in a password manager). This is more secure since both parts are needed to log in, at the cost of convenience.
Messages
Users’ key pairs are used for asymmetric encryption to exchange an ephemeral shared key (supported: AES-GCM) which is then used for symmetric encryption of messages.
Shared keys MUST expire after one hour to provide forward secrecy. This limits the impact of key compromise to a limited time window.
If the shared key has expired, the next user to send a message MUST generate a new shared key. The key is then encrypted using each member’s public key and each encrypted version is sent to the server.
Messages are additionally signed with the user’s private key using RSA-PSS (signature format: ${message_content}:${channel_id}:${unix_s}).
Sending a message requires both parts of the user’s key pair, public key so the correct encrypted shared key is used for encryption and private key for signing.
This prevents man-in-the-middle (MitM) attacks since replacing the public key of a member with an attacker’s public key would either invalidate the signature or decryption.
Flow diagram:
Read:
Client Server
| |
| /messages -------------> |
| <-------------- Messages |
| /key ------------------> |
| <--- Specific shared key |
| Decrypt shared |
| Decrypt message |
| Verify message |
| |
Send:
Client Server
| |
| /key ------------------> |
| <------- Last shared key |
| |
If expired:
| |
| Generate an AES key |
| Encrypt key per member |
| /keys -----------------> |
| | Save encrypted keys
| |
Finally:
| |
| Encrypt message |
| Sign message |
| /message --------------> |
| |
Keys
RSA-OAEP
The label below is an internal protocol constant, not a product name. It is kept as parley for wire compatibility.
- Label:
parley - Modulus length:
2048bits - Public exponent:
65537 - Hash:
SHA-256
RSA-PSS
- Salt length:
222bytes - Hash:
SHA-256
AES-GCM
- Key length:
128bits