I need a secure protocol to authenticate client side users with my server which has an API. I am devising something more secure particularly to resist active MITM attacks where an attacker may modify the request and perform some damaging function on the server. The general username/password model doesn't suit my purpose if it can be intercepted and the data modified in transit potentially by state sponsored adversaries.
I'm not sure if this is the right place to post it, maybe security.stackexchange is a better fit, but here goes. Please recommend any changes or improvements. A "+" indicates concatenation.
Design goals:
- Authenticate all API requests to the server to verify they are from the legitimate user.
- Authenticate all API responses from the server to verify the response came from the legitimate server not an attacker.
- Don't allow one user to spoof another user's requests to the server.
- Avert passive MITM attacks where an attacker tries to snoop the API credentials in transit.
- Avert active MITM attacks where an attacker attempts to send fake responses to the server or from the server.
- Avert replay attacks or rejecting a request/response if the MAC does not match.
- Allow each user to create a private key on the server. Only certain people can do this and this is the reason behind the user having knowledge of the server shared key.
- Each user to have a unique key on the server that no other user can use.
Assumptions:
- Server admin will own the server and potentially a user of the server as well.
- The server admin is a trustworthy person not interested in compromising/interfering with any of the other user's communications.
- Users of the server have an interest in keeping the shared key on the server a secret to protect their own communications so they won't give that key to anyone else.
Initial setup:
- A random 256 bit key ("shared key") is created and put in the source code on a server hosted on the local network, serving to wider internet.
- This shared key is given to each user of the server in person (not using a key exchange protocol or sent via insecure network). Most likely it is just a small group of users using the server.
- The shared key on its own can only be used to create accounts on the server.
- Client creates a 256 bit random nonce ("nonce").
Client creates 256 bit private key ("private key") using the hash based message authentication code with input in format HMAC(key, message):
private key = HMAC-SHA-256(shared key, nonce)
User creates a username ("username") which is just a random string of characters or psuedonym to uniquely identify their account.
User creates a message authentication code to:
a) verify they are allowed to create a user account on the server, and
b) to sign all the details sent to server to make sure nothing was modified.
MAC = HMAC-256(shared key, username + nonce)
Data sent to server to create account:
username
nonce
MAC
Server verifies user is allowed to create an account on the server and that all details weren't modified in transit by verifying the MAC:
MAC = HMAC-256(shared key, username + nonce)
Server will reject incorrect MACs or if that username is already taken.
- Server will check if the nonce is re-sent to reject replay attack.
If verified, server recreates the user's private key on server end using same process:
private key = HMAC-SHA-256(shared key, nonce)
If verified, server stores details in database for user:
username
private key
To verify an API request:
- User creates 256 bit per request nonce ("request nonce".
- User creates API request or group of variables to send to the server ("message packet"). This contains the variables that the server will process or store.
- Each request is sent with an API action "api action" to perform on the server. This prevents the attacker from changing what action to perform on the server with the same MAC.
User signs packet using:
MAC = HMAC-SHA-256(private key, nonce + sent timestamp + api action + username + message packet)
Client sends to server:
nonce
sent timestamp
api action
username
message packet
MAC
Server receives the request, looks up the username and retrieves the private key for the user.
Server verifies the request by:
MAC = HMAC-SHA-256(private key, nonce + sent timestamp + api action + username + message packet)
Server rejects invalid MACs, which will also mean any attempt to modify the nonce, timestamp, API action, message packet or MAC will fail.
- Server rejects messages received outside the UNIX timestamp range (5 seconds). The server and client are synchronized with UTC time.
- Server rejects duplicate messages/replay attacks received within same window (5 seconds) with same timestamp by storing the nonce temporarily.
- If the nonce is the same and same request is received twice, then the second one will be invalid. Sent nonces are kept on the server for 10 seconds and then discarded. A delay longer than this will obviously not be accepted due to the time delay.
- Failed requests are re-sent by the client using a different nonce, sent timestamp and MAC.
Server response:
On any server responses (including error responses for failed requests), the server signs the response with the user's private key so that the user knows the response is valid from the server.
nonce
sent timestamp
message packet
MAC = HMAC-SHA-256(private key, nonce + timestamp + message packet)
If MAC does not match on the client then the response is not actually from the server and will be discarded. A warning will be shown to the user that interference has occurred.
- Client will retain sent response nonces for 10 seconds to detect resent responses from attacker.
Hash(Key, Message)
I have separated what goes into the key and what goes into the message with a comma. I should have put that in the question. $\endgroup$HMAC-256(shared key, private key, username + nonce)
just two arguments? $\hspace{.43 in}$ $\endgroup$