An implementation of the Owl augmented PAKE protocol in Typescript, based on the Owl paper.
To install the package, run
npm install owl-ts
The following sections give an overview of how the protocol works and the corresponding code. A full demonstration can be found here.
The client and server must use the same configuration of elliptic curve and server identity. The configuration object can be created as follows
import { Config, Curves } from "owl-ts";
const config: Config = {
curve: Curves.P256,
serverId: "example.com",
};
The possible values of Curves
are Curves.P256
, Curves.P384
and Curves.P521
.
This configuration can then be used to set up the client and server.
import { OwlClient, OwlServer } from "owl-ts";
const client = new OwlClient(config);
const server = new OwlServer(config);
The Owl server and client use messages for input and output. Messages can be serialized to JSON with the serialize()
method. Messages can be deserialized using the deserialize()
method. This method takes either a JSON object or its string representation and returns a message object or a DeserializationError
if the input was not a valid message.
There are 6 message classes:
-
RegistrationRequest
- Contains values output byOwlClient.register
and used byOwlServer.register
-
UserCredentials
- Contains user credentials to be stored permanently in the database alongside the username -
AuthInitRequest
- Contains values output byOwlClient.authInit
and used byOwlServer.authInit
-
AuthInitialValues
- Temporary values output byOwlServer.authInit
and used byOwlServer.authFinish
to be stored in the database alongside the username. AfterOwlServer.authFinish
is complete these values can be deleted. -
AuthInitResponse
- Contains values output byOwlServer.authInit
and used byOwlClient.authFinish
-
AuthFinishRequest
- Contains values output byOwlClient.authFinish
and used byOwlServer.authFinish
Registration must be done over a secure connection such as HTTPS.
Firstly, pass the username and password to OwlClient.register
. This produces a RegistrationRequest
which is sent to the server.
const request = await client.register("username", "password");
const data = {
username: "username",
request: request.serialize(),
};
// send data to server
The server should check if the username is already in use, then use the OwlServer.register
method to produce a UserCredentials
object and store it in the database.
import { RegistrationRequest } from "owl-ts";
// assuming data is the JSON from the client's request
const { username, request } = data;
// check if user already exists
if (database[username]) {
// handle taken username
}
const regRequest = RegistrationRequest.deserialize(request, config);
if (regRequest instanceof DeserializationError) {
// handle invalid request
}
const credentials = await server.register(regRequest);
// save user record to database
database[username].credentials = credentials.serialize();
Authentication does not need to be done over a secure connection. It consists of four stages.
-
Use the
OwlClient.authInit
method to generate anAuthInitRequest
and send it to the server.const request = await client.authInit("username", "password"); const data = { username: "username", request: request.serialize(), }; // send data to server
-
The server retrieves the user's credentials and passes them and the
AuthInitRequest
toOwlServer.authInit
which returns an object of the form{ response: AuthInitResponse; initial: AuthInitialValues; }
initial
must be stored in the database forauthFinish
.response
must be sent to the client.// assuming data is the JSON from the client's request const { username, request } = data; // retrieve user credentials from database const user = database[username].credentials; // deserialize stored credentials const credentials = UserCredentials.deserialize(user, config); if (credentials instanceof DeserializationError) { // handle invalid credentials } // deserialize request const authRequest = AuthInitRequest.deserialize(request, config); if (authRequest instanceof DeserializationError) { // handle invalid request } // get initial auth values const authInit = await server.authInit(username, authRequest, credentials); if (authInit instanceof ZKPVerificationFailure) { // return ZKP verification error } const { initial, response } = authInit; // store initial values for authFinish database[username].initial = initial.serialize(); // send response.serialize() to the client
-
Pass the
AuthInitResponse
toclient.authFinish
. This returns an object of the form{ finishRequest: AuthFinishRequest; key: ArrayBuffer; kc: string; kcTest: string; }
finishRequest
is sent to the server,key
is a mutually derived key which can be used for symmetric encryption andkc
andkcTest
are values used for explicit key confirmation as covered later.const initResponse = AuthInitResponse.deserialize(result, config); if (initResponse instanceof DeserializationError) { // handle invalid response } const finish = await client.authFinish(initResponse); if (finish instanceof Error) { // handle error } const data = { username: "username", request: finish.finishRequest.serialize(), }; // send data to the server
-
The server retrieves the initial values and passes them and the
AuthFinishRequest
to theOwlServer.authFinish
method. If successful, it returns an object of the form{ key: ArrayBuffer; kc: string; kcTest: string; }
where
key
is the mutually derived key andkc
andkcTest
are the key confirmation values.// assuming data is the JSON from the client's request const { username, request } = data; // retrieve the initial values from the database const initialRaw = database[username].initial; // deserialize initial values const initial = AuthInitialValues.deserialize(initialRaw, config); if (initial instanceof DeserializationError) { // handle invalid data } // deserialize request const finishReq = AuthFinishRequest.deserialize(request, config); if (finishReq instanceof DeserializationError) { // handle invalid request } // finish auth, determine is user is authenticated const loginSuccess = await server.authFinish(username, finishReq, initial); if (loginSuccess instanceof Error) { // handle authentication failure } // otherwise, authentication is successful
Explicit key confirmation is an optional process which allows both parties to explicitly verify they have derived the same key before starting encrypted communications. Explicit key confirmation can be added to the protocol as follows:
-
The client sends their
kc
value alongside theirAuthFinishRequest
. The server can then verify if this matches theirkcTest
. -
The server sends their
kc
value after successful authentication. The client can then verify if this matches theirkcTest
.
To build the package, run
npm install
npm run build
It will be built to the lib
directory.
To test the code, run
npm test