Authentication
Excalibur uses Secure Remote Password (SRP) protocol combined with Proof-of-Possession (PoP) to ensure secure authentication.
Initial Authentication
Although some of the endpoints of the server is available without authentication, any client who wishes to access their files need to authenticate themselves with the server.
The initial authentication process uses a WebSocket connection to the /api/auth endpoint. The rough process is as follows:
Let us examine the process in more detail.
Message Format
Each message is a JSON object containing the following fields:
status: Current authentication status. Can beOK,ERR, or null.binary: Whether the included data is binary data. Can betrueorfalse.data: Message data. Ifbinaryis true, this field is a Base64 encoded byte array. Otherwise, it is a ASCII string.
Here are two examples of messages:
{
"status": "OK",
"binary": false,
"data": "U is OK"
}
{
"status": null,
"binary": true,
"data": "AQ=="
}
Specification
Let
- denote the SRP prime according to RFC5054, Appendix A;
- denote the SRP generator according to RFC5054, Appendix A;
- denote the SRP multiplier according to RFC5054, Section 2.6;
- denote the SRP- key generated on the client-side; and
- be the verifier value.
and should be treated as secrets. The server should never gain access to , and no one other than the server and client should gain access to .
The initial authentication process is as follows:
-
Client sends its username to the server.
-
Server checks whether it has a record of the requested username.
- Username not found: sends message with status
ERRand dataUser does not exist. Terminate connection.
- Username not found: sends message with status
-
Server sends message with status
OKand the SRP group size as an ASCII string (e.g.,1024). -
Server generates its ephemeral values (private value and public value ) and sends the public value to the client (setting
binary = true). -
Client checks received .
- If is invalid (e.g., ), sends message with status
ERR(data can be anything). Can choose to terminate, or wait for server to try another .
- If is invalid (e.g., ), sends message with status
-
Client generates its ephemeral values (private value and public value ) and sends the public value to the server (setting
binary = true) with statusOK. -
Server checks received .
- If is invalid (e.g., ), sends message with status
ERR(data can be anything). Can choose to terminate, or wait for client to try another .
- If is invalid (e.g., ), sends message with status
-
Both client and server computes where refers to concatenation. Note that and have been padded to SRP group size bits using zero padding.
- If server detects , sends message with status
ERRand dataShared U value is 0. Terminate connection. - If client detects , can just choose to close connection.
- If server detects , sends message with status
-
Server sends message with status
OKand dataU is OK. -
Client and server each computes their premaster:
- Client:
- Server:
The master is computed by computing SHA3-256 of the premaster, padded to the SRP group size bits using zero padding.
-
Client computes , where means XOR and is SHA3-256. Client sends to the server (setting
binary = true) with statusOK. -
Server computes its own value using the same formula (using in place of ), and checks with the received .
- If they do not match, sends message with status
ERRand dataM1 values do not match. Terminate connection.
- If they do not match, sends message with status
-
Server computes and sends it to the client (setting
binary = true) with statusOK. -
Client computes its own value using the same formula (using in place of ), and checks with the received .
- If they do not match, terminate connection.
-
Client sends message with status
OKwith no data. -
Server prepares authentication token in the form of a JWT. Encrypts it with shared master key using AES-GCM-256.
-
Server sends message with no status and JSON data containing three fields:
nonce,token, andtag.nonce: A random value, as a Base64 encoded byte array.token: The encrypted JWT, as a Base64 encoded byte array.tag: The authentication tag, as a Base64 encoded byte array.
-
Both sides close connection.
The full code that implements the server-side checking can be found in the comms.py file.
Authenticating Subsequent Requests
Once this initial authentication process is complete, future requests to secure endpoints will require the use of the authentication token obtained from the server. Do note that the body of the request and response will be encrypted using the Excalibur Encryption Format (ExEF).
Authentication Token
The authentication token is a JWT that contains the following claims:
sub: The subject of the token, which is the username.iat: The issued at time of the token, which is the current time.exp: The expiration time of the token.- Currently the token expires after one hour.
uuid: Comms UUID used in the Proof-of-Possession (PoP) process.
An example of JWT (which has an invalid JWT signature) is as follows:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInN1YiI6ICJNeUNvb2xVc2VybmFtZSIsCiAgICAiaWF0IjogMTUxNjIzOTAyMiwKICAgICJleHAiOiAxNTE2MjQyNjIyLAogICAgInV1aWQiOiAiMTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDAwIgp9.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
which has the following data (expressed in JSON):
{
"sub": "MyCoolUsername",
"iat": 1516239022,
"exp": 1516242622,
"uuid": "123e4567-e89b-12d3-a456-426614174000"
}
Proof-of-Possession (PoP)
A proof-of-possession (PoP) value needs to be computed in addition to providing the authentication token. This value is computed using a HMAC of the message <METHOD> <PATH> <TIMESTAMP> <NONCE>, where
<METHOD>is the HTTP method (e.g.,GET,POST,PUT,DELETE) in ALL CAPS;<PATH>is the path of the request (e.g.,/some/path/here);<TIMESTAMP>is the current time in seconds since the Unix epoch; and<NONCE>is a random 16 byte value.
The body of the message need not be included in the HMAC calculation since it is already verified by the encryption:
- If the request does not include a body, then there is already nothing to check;
- If the request does include a body, the body should be encrypted using AES-GCM, which authenticates the data sent. Since the data sent uses the secret master key, no malicious actor can spoof the data; thus no need to check.
The HMAC's key is the SRP master key and the hash algorithm used is SHA-256. The output PoP value is encoded using Base64 for ease of transmission.
A CyberChef demonstration of the PoP generation process can be found here.
Proving Possession
Requests to secure endpoints need both the authentication token and a PoP header, and the request/response body will be encrypted using the SRP master key.
To prove that the user is who they claim to be, their request will need to include two headers:
Authorization: The format of the header isBearer <JWT>where<JWT>is the authentication token.X-SRP-PoP: The format of the header is<TIMESTAMP> <NONCE> <PoP>where<PoP>is the Base64 encoded PoP value computed as described above and<TIMESTAMP>and<NONCE>are the timestamp and nonce used to compute the PoP value, respectively.
Failing to provide both headers will result in a 401 Unauthorized response.