This is an old revision of the document!
Table of Contents
Protocol
This document is still a draft, and is subject to change at any time!
The PawSD protocol is designed around a simple request-response architecture that allows it to work over a variety of underlying transports. All a transport has to provide is a way for a client to send some bytes to a server and for the server to send some bytes back in response. It is imperative to choose transports that provide some form of data integrity, i.e. preventing malicious third parties from modifying data in-transit between the client and server.
Unless otherwise specified, the protocol uses big-endian byte and bit order, and no padding.
Within this document, integers are described in a shorthand format: a letter “u” for unsigned or “s” for signed, followed by their length in bits. “Enums” are unsigned integers that take one of a defined set of values. Arrays are indicated by prefixing their item type with the number of items in square brackets, or an underscore if it's variable: [16]u8 means 16 arbitrary bytes and [_]record means a number of records specified by a previous field.
Requests and responses
A request is structured as follows:
| Field | Length | Type |
|---|---|---|
| request magic | 8 bytes | constant ASCII string “PawRqust” |
| protocol version | 2 bytes | u16 (current version is 1) |
| request verb | 2 bytes | enum |
| request payload | varies | according to verb |
And a response looks like this:
| Field | Length | Type |
|---|---|---|
| response magic | 8 bytes | constant ASCII string “PawRspns” |
| protocol version | 2 bytes | u16 (current version is 1) |
| status code | 2 bytes | enum |
| response payload | varies | according to status code |
For all status codes except “OK”, the response payload is a string (2 byte length prefix, UTF-8, no null terminator) describing the error in a manner useful to client developers. Friendly user interfaces are expected to provide their own localised descriptions of errors, but may choose to also show the server's description.
Status codes
The currently defined status codes for responses are (in hexadecimal):
- 0x0200: OK
- 0x0400: generic client error
- 0x0401–0x0403: TODO
- 0x0404: resource not found
- 0x0500: generic server error
- 0x0501: not implemented
- 0x0503: service temporarily unavailable
- 0x0505: unknown verb
Zones, services and records
Zones are purely logical and are not literally sent over the wire. However, services are identified by the combination of their containing zone's public key (and its type/cryptosystem) along with their index within the zone (starting from 0 for the “metadata service” containing info about the zone itself).
Services are transmitted as follows:
| Field | Length | Type |
|---|---|---|
| signature length | 2 bytes | u16 |
| signature | varies | [_]u8 |
| index | 2 bytes | u16 |
| flags | 4 bytes | bitfield |
| record count | 2 bytes | u16 |
| records | varies | [_]record |
The signature is calculated from the concatenation of the index, flags, record count, and records fields (i.e. the entire service in wire format except for the signature itself). The signature algorithm is not given in the service itself but will be specified as part of the service's identifier, such as when requesting it.
Records are just a list of tag-value pairs:
| Field | Length | Type |
|---|---|---|
| tag count | 2 bytes | u16 |
| tag1 | 16 bytes | [2]u64 |
| value1 length | 4 bytes | u32 |
| value1 | varies | [_]u8 |
| tag2, etc |
Signature algorithms
The only currently defined signature algorithm (or “public key type”) is Ed25519 (identified on the wire by u16 “25519”). All implementations are required to support it, and shouldn't implement any other algorithms except for testing purposes. This is the “pure” variant usually called ed25519 in crypto libraries, not “pre-hashed” (ed25519ph).
Verb 0: Echo
This is intended for troubleshooting purposes only. The server simply returns the same data back to the client.
Request and response payloads (identical):
| Field | Length | Type |
|---|---|---|
| data | 16 bytes | [16]u8 |
Verb 1: Fetch service
The client asks the server for a particular service. Even if the service exists, the server may not have it, in which case it should respond “resource not found” (and should not perform any lookups of its own to try to find it).
Request payload:
| Field | Length | Type |
|---|---|---|
| public key type | 2 bytes | enum |
| public key length | 2 bytes | u16 |
| public key | varies | [_]u8 |
| service index | 2 bytes | u16 |
Design note: although the length of the public key is implicitly given by the key type field (e.g. an ed25519 key will always be 32 bytes long), we still explicitly state its length, so that implementations that don't support a given key type can still parse the request properly, even if just to show a more useful error message.
Response payload:
| Field | Length | Type |
|---|---|---|
| service | varies | service |
TODO: other verbs!
Verb 4096 (0x1000): Start authentication
The client briefly identifies itself using a “client ID”, which is an arbitrary byte sequence that needs to uniquely but persistently identify the client. For example, it could be some hash of the concatenation of: the client's implementation name, a random number saved on the client's storage, and some information about the server such as its IP address (to prevent cross-server tracking). The client ID allows the server to keep track of the authentication session across subsequent requests. The client also asks for access to a particular set of scopes.
Request payload:
| Field | Length | Type |
|---|---|---|
| client ID | 16 bytes | [16]u8 |
| scopes | ??? | TODO |
The server then returns either a “challenge set”, or an auth token for the given scopes. The response payload therefore is:
| Field | Length | Type |
|---|---|---|
| completed? | 1 byte | boolean |
| token or challenges | varies | [32]u8 or challenges |
If “completed?” is true (1), the “token or challenges” field will be:
| Field | Length | Type |
|---|---|---|
| conjunction | 1 byte | enum (0 = or, 1 = and) |
| challenge count | 1 byte | u8 (≥ 1) |
| challenge1 ID | 16 bytes | [2]u64 |
| challenge1 data length | 4 bytes | u32 |
| challenge1 data | varies | [_]u8 |
| challenge2 ID, etc |
If the conjunction is “or”, the client may choose any one of the contained challenges to solve; if it's “and”, the client has to solve all of them. If the challenge count is 1, the conjunction is irrelevant. Consequently the client needs to send its challenge response(s) using the Continue authentication verb as follows.
Challenges
The structure of the “challenge data” and “response” fields are defined by each individual challenge. There is a non-exhaustive list of known challenges.
Verb 4097 (0x1001): Continue authentication
The client sends one or multiple responses to challenges previously given by the server. (Note confusing terminology: in this case the “responses” are responses in the sense of a challenge-response exchange, but they are sent as part of the request payload in the protocol.)
Request payload:
| Field | Length | Type |
|---|---|---|
| client ID | 16 bytes | [16]u8 |
| response count | 1 byte | u8 |
| challenge1 ID | 16 bytes | [2]u64 |
| response1 length | 4 bytes | u32 |
| response1 | varies | [_]u8 |
| challenge2 ID, etc |
The response payload is identical to the Start authentication verb.
