#ifndef __PAIR_AP_H__ #define __PAIR_AP_H__ #include #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__ */