Files
shairport-sync/pair_ap/pair.h
T

267 lines
11 KiB
C

#ifndef __PAIR_AP_H__
#define __PAIR_AP_H__
#include <stdint.h>
#define PAIR_AP_VERSION_MAJOR 0
#define PAIR_AP_VERSION_MINOR 14
#define PAIR_AP_DEVICE_ID_LEN_MAX 64
#define PAIR_AP_POST_PIN_START "POST /pair-pin-start"
#define PAIR_AP_POST_SETUP "POST /pair-setup"
#define PAIR_AP_POST_VERIFY "POST /pair-verify"
#define PAIR_AP_POST_ADD "POST /pair-add"
#define PAIR_AP_POST_LIST "POST /pair-list"
#define PAIR_AP_POST_REMOVE "POST /pair-remove"
enum pair_type
{
// This is the pairing type required for Apple TV device verification, which
// became mandatory with tvOS 10.2.
PAIR_CLIENT_FRUIT,
// This is the Homekit type required for AirPlay 2 with both PIN setup and
// verification
PAIR_CLIENT_HOMEKIT_NORMAL,
// Same as normal except PIN is fixed to 3939 and stops after setup step 2,
// when session key is established
PAIR_CLIENT_HOMEKIT_TRANSIENT,
// Server side implementation supporting both transient and normal mode,
// letting client choose mode. If a PIN is with pair_setup_new() then only
// normal mode will be possible.
PAIR_SERVER_HOMEKIT,
};
/* This struct stores the various forms of pairing results. The shared secret
* is used to initialise an encrypted session via pair_cipher_new(). For
* non-transient client pair setup, you also get a key string (client_setup_keys) from
* pair_setup_result() that you can store and use to later initialise
* pair_verify_new(). For non-transient server pair setup, you can either:
* - Register an "add pairing" callback (add_cb) with pair_setup_new(), and
* then save the client id and key in the callback (see server-example.c for
* this approach).
* - Check pairing result with pair_setup_result() and if successful read and
* store the client id and key from the result struct.
* - Decide not to authenticate clients during pair-verify (set get_cb to NULL)
* in which case you don't need to save client ids and keys from pair-setup.
*
* Table showing returned data (everything else will be zeroed):
*
* | pair-setup | pair-verify
* --------------------------------|-------------------------------|--------------
* PAIR_CLIENT_FRUIT | client keys | shared secret
* PAIR_CLIENT_HOMEKIT_NORMAL | client keys, server public | shared secret
| key, server id | shared secret
* PAIR_CLIENT_HOMEKIT_TRANSIENT | shared secret | n/a
* PAIR_SERVER_HOMEKIT (normal) | client public key, client id | shared secret
* PAIR_SERVER_HOMEKIT (transient) | shared secret | n/a
*/
struct pair_result
{
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; // ID of the peer
uint8_t client_private_key[64];
uint8_t client_public_key[32];
uint8_t server_public_key[32];
uint8_t shared_secret[64];
size_t shared_secret_len; // Will be 32 (normal) or 64 (transient)
};
struct pair_setup_context;
struct pair_verify_context;
struct pair_cipher_context;
typedef int (*pair_cb)(uint8_t public_key[32], const char *device_id, void *cb_arg);
typedef void (*pair_list_cb)(pair_cb list_cb, void *list_cb_arg, void *cb_arg);
/* ------------------------------- pair setup ------------------------------- */
/* Client
* When you have the pin-code (must be 4 chars), create a new context with this
* function and then call pair_setup() or pair_setup_request1(). device_id is
* only required for Homekit pairing. If the client previously paired
* (non-transient) and has saved credentials, it should instead skip setup and
* only do verification. The callback is only for Homekit, and you can leave it
* at NULL if you don't care about saving ID and key of the server for later
* verification (then you also set get_cb to NULL in pair_verify_new), or if you
* will read the id and key via pair_setup_result.
*
* Server
* The client will make a connection and then at some point make a /pair-setup
* or a /pair-verify. The server should:
* - new /pair-setup: create a setup context with a pin-code (or NULL to allow
* transient pairing), and then call pair_setup() to process request and
* construct reply (also for subsequent /pair-setup requests)
* - new /pair_verify: create a verify context and then call pair_verify()
* to process request and construct reply (also for subsequent /pair-verify
* requests)
*/
struct pair_setup_context *
pair_setup_new(enum pair_type type, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id);
void
pair_setup_free(struct pair_setup_context *sctx);
/* Returns last error message
*/
const char *
pair_setup_errmsg(struct pair_setup_context *sctx);
/* Will create a request (if client) or response (if server) based on the setup
* context and last message from the peer. If this is the first client request
* then set *in to NULL. Returns negative on error.
*/
int
pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
/* Returns the result of a pairing, or negative if pairing is not completed. See
* 'struct pair_result' for info about pairing results. The string is a
* representation of the result that is easy to persist and can be used to feed
* back into pair_verify_new. The result and string becomes invalid when you
* free sctx.
*/
int
pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx);
/* These are for constructing specific message types and reading specific
* message types. Not needed for Homekit pairing if you use pair_setup().
*/
uint8_t *
pair_setup_request1(size_t *len, struct pair_setup_context *sctx);
uint8_t *
pair_setup_request2(size_t *len, struct pair_setup_context *sctx);
uint8_t *
pair_setup_request3(size_t *len, struct pair_setup_context *sctx);
int
pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int
pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int
pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
/* ------------------------------ pair verify ------------------------------- */
/* Client
* When you have completed pair setup you get a string containing some keys
* from pair_setup_result(). Give the string as input to this function to create
* a verification context. Set the callback to NULL. Then call pair_verify().
* The device_id is required for Homekit pairing.
*
* Server
* When you get a pair verify request from a new peer, create a new context with
* client_setup_keys set to NULL, with a callback set and the server's device ID
* (same as for setup). Then call pair_verify(). The callback is used to get
* the persisted client public key (saved after pair setup), so the client can
* be verified. You can set the callback to NULL if you don't care about that.
* If set, the callback is made as part of pair_verify_response2. The job of the
* callback is to fill out the public_key with the public key from the setup
* stage (see 'struct pair_result'). If the client device id is not known (i.e.
* it has not completed pair-setup), return -1.
*/
struct pair_verify_context *
pair_verify_new(enum pair_type type, const char *client_setup_keys, pair_cb get_cb, void *cb_arg, const char *device_id);
void
pair_verify_free(struct pair_verify_context *vctx);
/* Returns last error message
*/
const char *
pair_verify_errmsg(struct pair_verify_context *vctx);
/* Will create a request (if client) or response (if server) based on the verify
* context and last message from the peer. If this is the first client request
* then set *in to NULL. Returns negative on error.
*/
int
pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *sctx, const uint8_t *in, size_t in_len);
/* Returns a pointer to the result of the pairing. Only the shared secret will
* be filled out. Note that the result become invalid when you free vctx.
*/
int
pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx);
/* These are for constructing specific message types and reading specific
* message types. Not needed for Homekit pairing where you can use pair_verify().
*/
uint8_t *
pair_verify_request1(size_t *len, struct pair_verify_context *vctx);
uint8_t *
pair_verify_request2(size_t *len, struct pair_verify_context *vctx);
int
pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
int
pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
/* ------------------------------- ciphering -------------------------------- */
/* When you have completed the verification you can extract a shared secret with
* pair_verify_result() - or, in case of transient pairing, from
* pair_setup_result(). Give the shared secret as input to this function to
* create a ciphering context.
*/
struct pair_cipher_context *
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
void
pair_cipher_free(struct pair_cipher_context *cctx);
/* Returns last error message
*/
const char *
pair_cipher_errmsg(struct pair_cipher_context *cctx);
/* The return value equals length of plaintext that was encrypted, so if the
* return value == plaintext_len then everything was encrypted. On error -1 is
* returned.
*/
ssize_t
pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);
/* The return value equals length of ciphertext that was decrypted, so if the
* return value == ciphertext_len then everything was decrypted. On error -1 is
* returned.
*/
ssize_t
pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx);
/* Rolls back the nonce
*/
void
pair_encrypt_rollback(struct pair_cipher_context *cctx);
void
pair_decrypt_rollback(struct pair_cipher_context *cctx);
/* --------------------------------- other ---------------------------------- */
/* These are for Homekit pairing where they are called by the controller, e.g.
* the Home app
*
* TODO this part is currenly not working
*/
int
pair_add(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb add_cb, void *cb_arg, const uint8_t *in, size_t in_len);
int
pair_remove(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb remove_cb, void *cb_arg, const uint8_t *in, size_t in_len);
int
pair_list(enum pair_type type, uint8_t **out, size_t *out_len, pair_list_cb list_cb, void *cb_arg, const uint8_t *in, size_t in_len);
/* For parsing an incoming message to see what type ("state") it is. Mostly
* useful for servers. Returns 1-6 for pair-setup and 1-4 for pair-verify.
*/
int
pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len);
/* For servers, pair_ap calculates the public key using device_id as a seed.
* This function returns that public key.
*/
void
pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id);
#endif /* !__PAIR_AP_H__ */