From c1fa8ffbcb6f88907ea941e4c619ca5af5fe41a4 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Sun, 8 Dec 2013 23:12:08 -0800 Subject: [PATCH] Created API Protocol (markdown) --- API-Protocol.md | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 API-Protocol.md diff --git a/API-Protocol.md b/API-Protocol.md new file mode 100644 index 0000000..9361489 --- /dev/null +++ b/API-Protocol.md @@ -0,0 +1,356 @@ +# Registration + +## Request a verification code + +``` +GET /v1/accounts/{transport}/code/{number} +``` + +The client requests an SMS or Voice verification code for the client's PSTN number. + +1. `transport` is the string `sms` or `voice`, depending on how the client would like a verification code delivered. +1. `number` is the client's PSTN number. + +**Returns**: + +1. `200` request was processed successfully. +1. `400` badly formatted `number`. +1. `415` invalid `transport`. +1. `413` rate limit exceeded. Too many requests. + +## Confirm a verification code + +``` +PUT /v1/acccounts/code/{verification_code} +Authorization: Basic {basic_auth} + +{ + "signalingKey" : "{bas64_encoded_52_byte_key}" + "supportsSms" : false +} +``` + +The client submits the verification code it received via voice or SMS to the server for confirmation. + +1. `verification_code` is the code it received via voice or SMS, numeric only. +1. `basic_auth` are the authorization credentials the client would like to create. These are in the form of `Base64({number}:{password})`, where `number` is the client's verified PSTN number and `password` is a randomly generated 16 byte string. +1. `signalingKey` is a randomly generated 32 byte AES key and a 20 byte HMAC-SHA1 MAC key, concatenated together and Base64 encoded. +1. `supportsSms` indicates whether a client supports SMS as a transport. + +**Returns**: + +1. `200` account successfully verified. +1. `401` badly formatted `basic_auth`. +1. `403` incorrect `verification_code`. +1. `413` rate limit exceeded. + +## Registering an APN or GCM id + +``` +PUT /v1/accounts/apn/ +Authorization: Basic {basic_auth} + +{ + apnRegistrationId: "{apn_registration_id}" +} +``` + +or + +``` +PUT /v1/accounts/gcm/ +Authorization: Basic {basic_auth} + +{ + gcmRegistrationId: "{gcm_registration_id}" +} +``` + +The client submits its APN or GCM push registration ID. + +1. `basic_auth` is the client's authorization credentials (see above). +1. `gcm_reistration_id` or `apn_registration_id` is the client's registration ID. + +**Returns**: + +1. `200` request succeeded. +1. `401` invalid authentication credentials. +1. `415` badly formatted JSON. + +## Registering prekeys + +``` +PUT /v1/keys/ +Authorization: Basic {basic_auth} + +{ + lastResortKey : { + keyId: 0xFFFFFF + publicKey: "{public_key}" + identityKey: "{identity_key}" + }, + keys: [ + { + keyId: {key_id}, + publicKey: "{public_key}", + identityKey: "{public_key}" + }, + ...] +} +``` + +1. `public_key` is a randomly generated Curve25519 public key with a leading byte of `0x05` to indicate its type. This is a total of 33 bytes, base64 encoded without padding (no ==). +1. `identity_key` is a Curve25519 public key with a leading byte of `0x05` to indicate its type. This is a total of 33 bytes, base64 encoded without padding (no ==). Each client should have a single identity key generated at install time. +1. `key_id` each prekey has a unique 24bit identifier. The last resort key is always 0xFFFFFF. + +**Returns**: + +1. `200` request succeeded. +1. `401` invalid authentication credentials. +1. `415` badly formatted JSON. + +## Getting a contact intersection + +``` +PUT /v1/directory/tokens +Authorization: Basic {basic_auth} +{ + "contacts": [{"{token}", "{token}", ..., "{token}"] +} +``` + +1. `token` is Base64(SHA1(E164number)[0:10]) without Base64 padding. + +**Returns**: + +1. `400` badly formatted token(s). +1. `401` invalid authentication credentials. +1. `415` badly formatted JSON. +1. `200` request succeeded. The structure below is returned. + +``` +{ + contacts: [{token="{token}", relay="{relay}", supportsSms="true"}, + {token="{token}"}, + ..., + {token="tokenN", relay="{relay}"}] +} +``` + +1. `token` is Base64(SHA1(E164number)[0:10]) without Base64 padding. +1. `relay` is the name of a federated node which this contact is associated with. +1. `supportsSms` indicates that the contact supports the SMS transport. + +At this point the client should be fully registered. + +# Sending Messages + +## Message Format + +Messages bodies sent and received by clients are a protocol buffer structure: + +``` +message PushMessageContent { + optional string body = 1; + + message AttachmentPointer { + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; + } + + repeated AttachmentPointer attachments = 2; +} +``` + +## Getting a recipient's PreKey + +If a client does not have an existing session with a recipient, the client will need to retrieve a PreKey for the recipient in order to start one. + +``` +GET /v1/keys/{number}?relay={relay} +Authorization: Basic {basic_auth} +``` + +1. `number` is the PSTN number of the recipient. +1. `relay` (optional) is the federated relay the recipient is associated with. The `relay` param should only be included if the destination is at a federated node other than the sender. + +**Returns**: + +1. `401` invalid authentication credentials. +1. `413` rate limit exceeded. +1. `404` unknown/unregistered `number`. +1. `200` request succeeded. The structure below is returned. + +``` +{ + keyId: {key_id}, + publicKey: "{public_key}", + identityKey: "{public_key}" +} +``` + +## Submitting a message + +``` +POST /v1/messages/ +Authorization Basic {basic_auth} + +{ + messages: [{ + type: {type}, + destination: "{destination_number}", + body: "{base64_encoded_message_body}", // Encrypted PushMessageContent + relay: "{relay}", + timestamp: "{time_sent_millis_since_epoc}" + }, + ..., + ] +} +``` + +1. `type` is the type of message. Supported types are enumerated below. +1. `destination_number` is the PSTN number of the message recipient. +1. `body` is the Base64 encoded (without padding) and encrypted `PushMessageContent` (above). +1. `relay` (optional) is the relay the message recipient is registered with. +1. `timestamp_sent_millis_since_epoch` is the timestamp of the message in millis since the epoch. + +**Returns**: + +1. `401` invalid authentication credentials. +1. `413` rate limit exceeded. +1. `415` badly formatted JSON. +1. `200` request succeeded. The structure below is returned. + +``` +{ + "success" : [{destination_number}, {destination_number}, ..., {destination_number}], + "failure" : [{destination_number},...,{destination_number}] +} +``` + +Supported types: + +``` +int TYPE_MESSAGE_PLAINTEXT = 0; +int TYPE_MESSAGE_CIPHERTEXT = 1; +int TYPE_MESSAGE_PREKEY_BUNDLE = 3; +``` + +## Receiving a message + +APN clients will receive a push notification: +``` +{ + alert: "You have a new message!", + "m": "{payload}" +} +``` + +GCM clients will receive a push notification: +``` +{payload} +``` + +1. `payload` is a Base64 encoded (without padding) `IncomingPushMessageSignal`, which is encrypted and MAC'd using the `signalingKey` submitted during registration. + +Encrypted IncomingPushMessageSignal format: + +``` +struct { + opaque version[1]; + opaque iv[16]; + opaque ciphertext[...]; // The IncomingPushMessageSignal + opaque mac[10]; +``` + +The IncomingPushMessageSignal protocol buffer: + +``` +message OutgoingMessageSignal { + optional uint32 type = 1; + optional string source = 2; + optional string relay = 3; + repeated string destinations = 4; + optional uint64 timestamp = 5; + optional bytes message = 6; // Encrypted PushMessageContent (above) +} +``` + +# Attachments + +Recall that a push message is transmitted as the following structure: + +``` +message PushMessageContent { + optional string body = 1; + + message AttachmentPointer { + optional fixed64 id = 1; + optional string contentType = 2; + optional bytes key = 3; + } + + repeated AttachmentPointer attachments = 2; +} +``` + +To fill out the `AttachmentPointer` structure, the client takes the following steps: + +1. Generates a single-use 32 byte AES key and 32 byte Hmac-SHA256 key. +1. Encrypts the attachment using AES in CBC mode with PKCS#5 padding and a random IV, then formats the encrypted blob as `IV || Ciphertext || MAC`. +1. Requests an attachment allocation from the server. +1. Uploads the attachment to the allocation. +1. Constructs the `AttachmentPointer` with the attachment allocation `id`, the attachment's MIME `contentType`, and the concatenated 32 byte AES and 32 byte Hmac-SHA256 `key`. + +## Allocating an attachment + +``` +GET /v1/attachments/ +Authorization: {basic_auth} +``` + +**Returns**: + +1. `401` invalid authentication credentials. +1. `413` rate limit exceeded. +1. `200` request succeeded. The structure below is returned. + +``` +{ + "id" : "{attachment_id}", + "location" : "{attachment_url}" +} +``` + +## Uploading an attachment + +``` +PUT {attachment_url} +Content-Type: application/octet-stream +``` + +The client `PUT`s the encrypted binary blob to the `attachment_url` returned from the attachment allocation step. + +## Retrieving an attachment + +``` +GET /v1/attachments/{attachment_id} +Authorization: {basic_auth} +``` + +1. `attachment_id` is the `id` in a received `AttachmentPointer` protocol buffer. + +**Returns** + +1. `401` invalid authentication credentials. +1. `413` rate limit exceeded. +1. `200` request succeeded. The structure below is returned. + +``` +{ + "location" : "{attachment_url}" +} +``` + +The client can now `GET {attachment_url}` to retrieve the encrypted binary blob. \ No newline at end of file