-
Notifications
You must be signed in to change notification settings - Fork 2
Security Model
When a the nodejs server is loaded:
- a cryptographic
server-secret-id
random string is generated - a cryptographic
server-secret
random string is generated (this secret is never sent to the client) -
email-server-secret
this is a hardcoded email server secret known both by the nodejs servers and the invitation email server
When the home page loads:
- a
server-session-id
random string is generated by the server (and is given to the client javascript) - a
server-session-id-validation
string is generated by the server (and is given to the client javascript) using the following algorithm: hash(server-secret-id
+ ":" +server-session-id
+ ":" +server-secret
) - the previously generated
server-secret-id
is sent to the javascript client - a cryptographic
server-session-salt
random string is generated by the server (and is given to the client javascript) - a cryptographic
server-session-secret
random string is generated by the server (and is given to the client javascript) - a cryptographic
client-session-secret
random string is generated by the client javascript (which is NOT sent to the server)
NOTE: The server-secret
is NEVER given to the javascript client.
The end-user picks a question from the preset questions (or types their own). The answer is then normalized.
The answer
normalization process:
- strip all pre/post white space from the string
- collapse all white space between words to a single space
- collapse spaces before and after and
-
symbol - make all characters uppercase (or perhaps lowercase; doesn't matter but we have to be consistent)
- remove any trailing punctuation (no
.
or?
) - ensure the minimum 4 character rule is satisfied
NOTE: All of this normalization processing must be done entire on the client side javascript as the server will never receive the answer.
The javascript client must generate proof for the remote party to prove that the remote party's javascript client indeed know the correct answer
.
Generating proof of the correct answer:
answer-proof
= hash("answer:" +server-secret-id
+ ":" +server-session-id
+ ":" +server-session-id-validation
+ ":" +server-session-salt
+ ":" +server-session-secret
+ ":" +client-session-secret
+ ":" +answer
+ ":end")
This proof is used to validate both client know the same answer.
Generate the client-to-client encryption/decryption cipher key:
shared-secret
= hash("cipher:" +server-secret-id
+ ":" +server-session-id
+ ":" +server-session-id-validation
+ ":" +server-session-salt
+ ":" +server-session-secret
+ ":" +client-session-secret
+ ":" +answer
+ ":end")
This shared secret cipher key is the master key used to encrypt or decrypt data send between two clients. The server does not have the client-session-key
and thus the server will not be able to decrypt any data without this key.
Client sends the following information to the server to create a new session:
server-session-id
server-session-id-validation
server-session-salt
server-session-key
answer-proof
- The question (encrypted) + question salt + validation:
question-salt
= cryptographically randomly generated string on client
encrypted-question
= encrypt(question-secret
,question
)
question-secret
= hash("secret:" +question-salt
+ ":" +client-session-secret
)
question-secret-validation
= hash("validate:" +question-salt
+ ":" +client-session-secret
)
question-integrity
= hmac(question-secret
,plain-text-question
)
- The message (encrypted) + message salt + validation:
message-salt
= cryptographically randomly generated string on client
encrypted-message
= encrypt(message-secret
,message
)
message-secret
= hash("secret:" +message-salt
+ ":" +shared-secret
)
message-secret-validation
= hash("validate:" +message-salt
+ ":" +shared-secret
)
message-integrity
= hmac(message-secret
,plain-text-message
)
- To/from email/name information
NOTE: Each message sent will get a brand new randomly generated salt. The question-secret
and the message-secret
are never sent to the server.
The server will validate the creation by verifying:
- The
server-session-id
is brand new (this must be an atomic check) - Generates
check-server-session-id-validation
and compares equal-toserver-session-id-validation
check-server-session-id-validation
= hash(server-secret-id
+ ":" +server-session-id
+ ":" +server-secret
)
The server will store the client provided information into a session record and return the following information:
-
email-server-nonce
- a random string generated by the server -
email-server-secret-proof
= hash("email-proof:" +email-server-secret
+ ":" +email-server-nonce
+ ":" +email-server-secret-expiry
+ ":" +to-email-address
) -
email-server-secret-expiry
- a unix epoch based time stamp of when this email proof will expire.
The URL to share with the remote party will contain the following:
chat-url
= https://trustinchat.com/path/server-session-id#client-session-secret
As the information following the anchor (#) on a URL is not supposed to be sent by a browser to a server (but can be read by JavaScript), the remote client JavaScript will have a piece of information in the URL never sent to the server which is part of the message secret.
The concern is this URL must be sent to the remote party in the invitation email and that could mean the server has access to the client-session-secret
which is part of the URL to be emailed. To minimize this risk the request to send the invitation email is an independent request to the server sent to an entirely different and independent server machine.
The request to send the invitation email must include the following:
- To/from email/name information
chat-url
-
email-server-nonce
- a random string generated by the server -
email-server-secret-proof
= hash("email-proof:" +email-server-secret
+ ":" +email-server-nonce
+ ":" +email-server-secret-expiry
+ ":" +to-email-address
) -
email-server-secret-expiry
- a unix epoch based time stamp of when this email proof will expire.
The server will perform the following validation steps:
- Check to see that the
email-server-nonce
has never been used before (this check must be atomic). - Ensure
check-email-server-secret-proof
equalsemail-server-secret-proof
wherecheck-email-server-secret-proof
= hash("email-proof:" +email-server-secret
+ ":" +email-server-nonce
+ ":" +email-server-secret-expiry
) - Ensure the
email-server-secret-expiry
has not already expired.
If this information all passes, the invitation email is sent.
The remote party will have to prove to the server that it has the correct answer
. When the page is loaded with the session id the following information is sent in the remote user's javascript page:
server-session-id
server-session-id-validation
server-session-salt
server-session-key
- The
question
(encrypted) + question salt:
question-salt
= cryptographically randomly generated string on client
encrypted-question
= encrypt(question-secret
,question
)
question-secret
= hash("secret:" +question-salt
+ ":" +client-session-secret
)
question-validation
= hash("validate:" +question-salt
+ ":" +client-session-secret
)
question-integrity
= hmac(question-secret
,plain-text-question
)
NOTE: The message and message salt are NOT included. The proof must fist be provided of the correct answer
to the server. The server will enforce the number of attempts has not been exceeded. If the number of attempts are exceeded the message is destroyed.
The client Javascript will decrypt the question
and present to the end user if question-validation
matches against check-question-validation
= hash("validate:" + question-salt
+ ":" + client-session-secret
). The question once decrypted integrity is then checked to ensure the decrypted message has been decrypted properly before being presented to the end user with check-question-integrity
= hmac(question-secret
, plain-text-question
).
If the client Javascript cannot validate the check-question-validation
against the question-validation
then the session is assumed invalid and an error is presented to the end user.
When the user enters the answer
the answer passes through the same validation process as when the answer
was first created. This ensures both users have entered the same information.
The JavaScript must send this to the server to provide evidence it knows the correct answer
:
-
answer-proof
= hash("answer:" +server-secret-id
+ ":" +server-session-id
+ ":" +server-session-id-validation
+ ":" +server-session-salt
+ ":" +server-session-secret
+ ":" +client-session-secret
+ ":" +answer
+ ":end")
If this matches the answer-proof
the server has stored for the session then sent to the client:
- The message (encrypted) + message salt + validation:
message-salt
= cryptographically randomly generated string on client
encrypted-message
= encrypt(message-secret
,message
)
message-secret
= hash("secret:" +message-salt
+ ":" +shared-secret
)
message-validation
= hash("validate:" +message-salt
+ ":" +shared-secret
)
message-integrity
= hmac(message-secret
,plain-text-message
)
The Javascript client then knows it has the correct answer
and can generate the shared-secret
:
shared-secret
= hash("cipher:" +server-secret-id
+ ":" +server-session-id
+ ":" +server-session-id-validation
+ ":" +server-session-salt
+ ":" +server-session-secret
+ ":" +client-session-secret
+ ":" +answer
+ ":end")
The Javascript client can then validate the message-validation
in the same manner as the question validation and if it passes then decrypt the message then validate the decrypted message integrity and display the message to the end user.
Each subsequent message sent between users uses the shared-secret
to generate new message-salt
for each message which can be used to create a new encrypted message. The message-salt
must NEVER be reused twice, thus the random salt must be truly cryptographically randomly generated and sufficiently long in number of characters.
The new message (encrypted) + message salt + validation:
new-message-salt
= cryptographically randomly generated string on client
new-encrypted-message
= encrypt(message-secret
,message
)
new-message-secret
= hash("secret:" +message-salt
+ ":" +shared-secret
)
new-message-validation
= hash("validate:" +message-salt
+ ":" +shared-secret
)
new-message-integrity
= hmac(new-message-secret
,new-plain-text-message
)
The server's job is then to relay the encrypted messages and temporarily store the encrypted versions of the messages on the server until the other party has verified the message has been delivered to the end user.
- Chat sessions should have a maximum lifetime from the point of creation.
- A cookie can be used to store the
answer-proof
andshared-secret
for either party when it is certain it has the correct information. This might be needed to handle page reload logic. - Not all information needed to do the server messaging is included in this document. Only the security aspects are included to illustrate how to create a secure chat session.