From: Mike Brady <4265913+mikebrady@users.noreply.github.com> Date: Wed, 7 Jul 2021 14:11:32 +0000 (+0000) Subject: Include pair_ap as a simple directory. X-Git-Tag: 4.1-dev~27 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=94e47fdf9ef110851ee273e6396bd71bbe886d08;p=thirdparty%2Fshairport-sync.git Include pair_ap as a simple directory. --- diff --git a/pair_ap/README.md b/pair_ap/README.md new file mode 100644 index 00000000..1c855444 --- /dev/null +++ b/pair_ap/README.md @@ -0,0 +1,72 @@ +# pair_ap +C client implementation of pairing for: +* Apple TV device verification, which became mandatory with tvOS 10.2 (this is + called fruit mode in pair_ap) +* Homekit pairing (for AirPlay 2, not working for Home app) + +Credit goes to @funtax and @ViktoriiaKh for doing some of the heavy lifting. +## Requirements +- libsodium +- libgcrypt or libopenssl +- libplist (only for Apple TV device verification) + +To build the example client and server you also need libevent2. If the +dependencies are met you can build simply by running 'make'. + +## Homekit pairing +Since I haven't been able to find much information on the internet on how +Homekit pairing is designed, here is a write-up of my current understanding. If +you know better, please help improve this. + +With Homekit pairing, there may be a controller (e.g. the Home app), and a +number of devices/accessories (e.g. speakers). The controller acts as a client +and can make requests for pairing. After it is paired it can also add other +"third-party" pairings to the device, it can remove pairings and it can ask for +a list of pairings. + +Other parties, e.g. the Music app or just iOS as an Airplay sender, can also +pair with devices/accesssories in a similar manner, but they are not full- +fledged Homekit controllers and thus don't make requests for adding, removing +or listing pairings. + +The controller uses `/pair-add` to make sure that all devices on a network get +the ID and public key of all the other devices, so that the user only needs to +pair a device once. + +### Normal pairing +For a normal first-time pairing, the client needs a one-time code (the device +announces via mDNS whether a code is required). The client calls +`/pair-pin-start` and the device displays the code. There is also QR-based +pairing, which is (probably?) an encoded code. + +After obtaining the code, the client initiates a three step `/pair-setup` +sequence, which results in both peers registering each other's ID and public +key. Henceforth, a pairing is verified with the two step `/pair-verify`, where +the parties check each-others identify. Saving the peer's ID + public key isn't +strictly necessary if client or server doesn't care about verifying the peer, +i.e. that `/pair-setup` has actually been completed. + +The result of `/pair-verify` is a shared secret that is used for symmetric +encryption of the following communinacation between the parties. + +### Transient pairing +Some devices don't require a code from the user for pairing (e.g. an Airport +Express 2). If so, the client just needs to go through a two-step `/pair-setup` +sequence which results in a shared secret, which is then used for encrypted +communication. A fixed code of 3939 is used. + +Such devices don't appear to be fully Homekit compatible - they will not, for +instance - appear in the Home app. + +## "fruit" pairing +Like normal Homekit pairing, this consists of first requesting a code with +`/pair-pin-start`, then a three-step `/pair-setup` and finally a two-step +`/pair-verify`. After that the communication is encrypted with the resulting +shared secret. + + +## Acknowledgments +- [AirPlayAuth](https://github.com/funtax/AirPlayAuth) +- [AirPlayAuth-ObjC](https://github.com/ViktoriiaKh/AirPlayAuth-ObjC) +- [ap2-sender](https://github.com/ViktoriiaKh/ap2-sender) +- [csrp](https://github.com/cocagne/csrp) diff --git a/pair_ap/client-example.c b/pair_ap/client-example.c new file mode 100644 index 00000000..35e424ad --- /dev/null +++ b/pair_ap/client-example.c @@ -0,0 +1,578 @@ +#include +#include +#include +#include + +#include +#include + +#include "evrtsp/evrtsp.h" +#include "pair.h" + +#define DEVICE_ID "AABBCCDD11223344" +#define ACTIVE_REMOTE "3515324763" +#define DACP_ID "FF1DB45949E6CBD3" +#define USER_AGENT "AirPlay/381.13" + +#define ENDPOINT_SETUP_FRUIT "/pair-setup-pin" +#define CONTENTTYPE_SETUP_FRUIT "application/x-apple-binary-plist" + +#define ENDPOINT_SETUP_HOMEKIT "/pair-setup" +#define CONTENTTYPE_SETUP_HOMEKIT "application/octet-stream" + + +typedef void (*request_cb)(struct evrtsp_request *, void *); + +static struct event_base *evbase; +static struct evrtsp_connection *evcon; +static int cseq; + +static const char *endpoint_setup; +static const char *content_type_setup; + +static enum pair_type pair_type; + +static struct pair_cipher_context *cipher_ctx; +static struct pair_verify_context *verify_ctx; +static struct pair_setup_context *setup_ctx; + + +static char * +prompt_pin(void) +{ + char *pin = NULL; + size_t len; + + printf ("Enter pin: "); + fflush (stdout); + + len = getline(&pin, &len, stdin); + if (len != 5) // Includes EOL + { + printf ("Bad pin length %zu\n", len); + return NULL; + } + + return pin; +} + +static int +response_process(uint8_t **response, struct evrtsp_request *req) +{ + if (!req) + { + printf("failed, could not read response\n"); + return -1; + } + else if (req->response_code != 200) + { + printf("failed with error code %d: %s (body is %zu bytes)\n\n", req->response_code, req->response_code_line, evbuffer_get_length(req->input_buffer)); + return -1; + } + + printf("success\n\n"); + + *response = evbuffer_pullup(req->input_buffer, -1); + + return evbuffer_get_length(req->input_buffer); +} + +static int +make_request(const char *url, const void *data, size_t len, const char *content_type, request_cb cb) +{ + struct evrtsp_request *req; + char buffer[1024]; + + req = evrtsp_request_new(cb, NULL); + + if (data) + evbuffer_add(req->output_buffer, data, len); + + if (content_type) + evrtsp_add_header(req->output_headers, "Content-Type", content_type); + + cseq++; + snprintf(buffer, sizeof(buffer), "%d", cseq); + evrtsp_add_header(req->output_headers, "CSeq", buffer); + + evrtsp_add_header(req->output_headers, "User-Agent", USER_AGENT); +// evrtsp_add_header(req->output_headers, "DACP-ID", DACP_ID); +// evrtsp_add_header(req->output_headers, "Active-Remote", ACTIVE_REMOTE); + + if (pair_type == PAIR_CLIENT_HOMEKIT_NORMAL) + evrtsp_add_header(req->output_headers, "X-Apple-HKP", "3"); + else if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT) + evrtsp_add_header(req->output_headers, "X-Apple-HKP", "4"); + + printf("Making request %d to '%s'... ", cseq, url); + + return evrtsp_make_request(evcon, req, EVRTSP_REQ_POST, url); +} + +static int +make_request_options(const char *url, const void *data, size_t len, const char *content_type, request_cb cb) +{ + struct evrtsp_request *req; + char buffer[1024]; + + req = evrtsp_request_new(cb, NULL); + + if (data) + evbuffer_add(req->output_buffer, data, len); + + if (content_type) + evrtsp_add_header(req->output_headers, "Content-Type", content_type); + + cseq++; + snprintf(buffer, sizeof(buffer), "%d", cseq); + evrtsp_add_header(req->output_headers, "CSeq", buffer); + + evrtsp_add_header(req->output_headers, "User-Agent", USER_AGENT); +// evrtsp_add_header(req->output_headers, "DACP-ID", DACP_ID); +// evrtsp_add_header(req->output_headers, "Active-Remote", ACTIVE_REMOTE); + evrtsp_add_header(req->output_headers, "X-Apple-HKP", "3"); + + printf("Making request %d to '%s'... ", cseq, url); + + return evrtsp_make_request(evcon, req, EVRTSP_REQ_OPTIONS, url); +} + + +static void +options_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + int ret; + + ret = response_process(&response, req); + if (ret >= 0) + printf("OPTIONS complete\n"); + + printf("Done\n"); + + event_base_loopbreak(evbase); +} + +static int +options_request(void) +{ + return make_request_options("*", NULL, 0, NULL, options_response); +} + +static void +rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt) +{ + uint8_t *out = NULL; + size_t out_len = 0; + ssize_t processed; + + uint8_t *in = evbuffer_pullup(evbuf, -1); + size_t in_len = evbuffer_get_length(evbuf); + + if (encrypt) + processed = pair_encrypt(&out, &out_len, in, in_len, cipher_ctx); + else + processed = pair_decrypt(&out, &out_len, in, in_len, cipher_ctx); + + evbuffer_drain(evbuf, in_len); + + if (processed < 0) + { + printf("Error while ciphering: %s\n", pair_cipher_errmsg(cipher_ctx)); + return; + } + else if (processed != in_len) + { + printf("Partial ciphering, only %zd of %zu input bytes were processed\n", processed, in_len); + } + + evbuffer_add(evbuf, out, out_len); +} + +static void +verify_step2_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + struct pair_result *result; + int ret; + + ret = response_process(&response, req); + if (ret < 0) + goto error; + + ret = pair_verify_response2(verify_ctx, response, ret); + if (ret < 0) + goto error; + + printf("Verify complete!\n\n"); + + ret = pair_verify_result(&result, verify_ctx); + if (ret < 0) + goto error; + + cipher_ctx = pair_cipher_new(pair_type, 0, result->shared_secret, result->shared_secret_len); + if (!cipher_ctx) + goto error; + + evrtsp_connection_set_ciphercb(evcon, rtsp_cipher, NULL); + + ret = options_request(); + if (ret < 0) + goto error; + + pair_verify_free(verify_ctx); + + return; + + error: + printf("Error: %s\n", pair_verify_errmsg(verify_ctx)); + pair_verify_free(verify_ctx); + pair_cipher_free(cipher_ctx); + event_base_loopbreak(evbase); +} + +static int +verify_step2_request(void) +{ + uint8_t *request; + size_t len; + int ret; + + request = pair_verify_request2(&len, verify_ctx); + if (!request) + return -1; + + ret = make_request("/pair-verify", request, len, "application/octet-stream", verify_step2_response); + + free(request); + + return ret; +} + +static void +verify_step1_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + int ret; + + ret = response_process(&response, req); + if (ret <= 0) + goto error; + + ret = pair_verify_response1(verify_ctx, response, ret); + if (ret < 0) + goto error; + + ret = verify_step2_request(); + if (ret < 0) + goto error; + + return; + + error: + printf("Error: %s\n", pair_verify_errmsg(verify_ctx)); + pair_verify_free(verify_ctx); + event_base_loopbreak(evbase); +} + +static int +verify_step1_request(const char *authorisation_key) +{ + uint8_t *request = NULL; + size_t len; + int ret; + + verify_ctx = pair_verify_new(pair_type, authorisation_key, NULL, NULL, DEVICE_ID); + if (!verify_ctx) + return -1; + + request = pair_verify_request1(&len, verify_ctx); + if (!request) + goto error; + + ret = make_request("/pair-verify", request, len, "application/octet-stream", verify_step1_response); + if (ret < 0) + goto error; + + free(request); + return ret; + + error: + printf("Error: %s\n", pair_verify_errmsg(verify_ctx)); + pair_verify_free(verify_ctx); + free(request); + return -1; +} + +static void +setup_step3_response(struct evrtsp_request *req, void *arg) +{ + const char *key; + uint8_t *response; + struct pair_result *result; + int ret; + + ret = response_process(&response, req); + if (ret <= 0) + goto error; + + ret = pair_setup_response3(setup_ctx, response, ret); + if (ret < 0) + goto error; + + ret = pair_setup_result(&key, &result, setup_ctx); + if (ret < 0) + goto error; + + printf("Setup of device ID %s complete, got key: %s\n", result->device_id, key); + + ret = verify_step1_request(key); + if (ret < 0) + goto error; + + pair_setup_free(setup_ctx); + + return; + + error: + printf("Error: %s\n", pair_setup_errmsg(setup_ctx)); + pair_setup_free(setup_ctx); + event_base_loopbreak(evbase); +} + +static int +setup_step3_request(void) +{ + uint8_t *request; + size_t len; + int ret; + + request = pair_setup_request3(&len, setup_ctx); + if (!request) + return -1; + + ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step3_response); + + free(request); + + return ret; +} + +static void +setup_step2_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + struct pair_result *result; + int ret; + + ret = response_process(&response, req); + if (ret <= 0) + goto error; + + ret = pair_setup_response2(setup_ctx, response, ret); + if (ret < 0) + goto error; + + printf("Setup SRP stage complete\n"); + + if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT) + { + ret = pair_setup_result(NULL, &result, setup_ctx); + if (ret < 0) + goto error; + + cipher_ctx = pair_cipher_new(pair_type, 0, result->shared_secret, result->shared_secret_len); + if (!cipher_ctx) + goto error; + + evrtsp_connection_set_ciphercb(evcon, rtsp_cipher, NULL); + + ret = options_request(); + if (ret < 0) + goto error; + + pair_setup_free(setup_ctx); + + return; + } + + ret = setup_step3_request(); + if (ret < 0) + goto error; + + return; + + error: + printf("Error: %s\n", pair_setup_errmsg(setup_ctx)); + pair_setup_free(setup_ctx); + event_base_loopbreak(evbase); +} + +static int +setup_step2_request(void) +{ + uint8_t *request; + size_t len; + int ret; + + request = pair_setup_request2(&len, setup_ctx); + if (!request) + return -1; + + ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step2_response); + + free(request); + + return ret; +} + +static void +setup_step1_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + int ret; + + ret = response_process(&response, req); + if (ret <= 0) + goto error; + + ret = pair_setup_response1(setup_ctx, response, ret); + if (ret < 0) + goto error; + + ret = setup_step2_request(); + if (ret < 0) + goto error; + + return; + + error: + printf("Error: %s\n", pair_setup_errmsg(setup_ctx)); + pair_setup_free(setup_ctx); + event_base_loopbreak(evbase); +} + +static int +setup_step1_request(void) +{ + uint8_t *request; + size_t len; + int ret; + + request = pair_setup_request1(&len, setup_ctx); + if (!request) + return -1; + + ret = make_request(endpoint_setup, request, len, content_type_setup, setup_step1_response); + + free(request); + return ret; +} + +static void +setup_start_response(struct evrtsp_request *req, void *arg) +{ + uint8_t *response; + char *pin = NULL; + int ret; + + if (req) + { + ret = response_process(&response, req); + if (ret < 0) + goto error; + } + + if (pair_type != PAIR_CLIENT_HOMEKIT_TRANSIENT) + { + pin = prompt_pin(); + if (!pin) + goto error; + } + + setup_ctx = pair_setup_new(pair_type, pin, NULL, NULL, DEVICE_ID); + if (!setup_ctx) + goto error; + + ret = setup_step1_request(); + if (ret < 0) + goto error; + + free(pin); + return; + + error: + if (setup_ctx) + printf("Error: %s\n", pair_setup_errmsg(setup_ctx)); + free(pin); + pair_setup_free(setup_ctx); + event_base_loopbreak(evbase); +} + +static int +setup_start_request(void) +{ + return make_request("/pair-pin-start", NULL, 0, "application/x-apple-binary-plist", setup_start_response); +} + + +int +main( int argc, char * argv[] ) +{ + int ret; + + if (argc < 4 || argc > 5) + { + printf("%s ip_address port homekit|fruit|transient [skip_pin]\n", argv[0]); + return -1; + } + + const char *address = argv[1]; + const char *port = argv[2]; + int skip_pin = (argc == 5); + + if (strcmp(argv[3], "fruit") == 0) + { + printf("Pair type is fruit\n"); + pair_type = PAIR_CLIENT_FRUIT; + endpoint_setup = ENDPOINT_SETUP_FRUIT; + content_type_setup = CONTENTTYPE_SETUP_FRUIT; + } + else if (strcmp(argv[3], "homekit") == 0) + { + printf("Pair type is homekit (normal)\n"); + pair_type = PAIR_CLIENT_HOMEKIT_NORMAL; + endpoint_setup = ENDPOINT_SETUP_HOMEKIT; + content_type_setup = CONTENTTYPE_SETUP_HOMEKIT; + } + else if (strcmp(argv[3], "transient") == 0) + { + printf("Pair type is homekit (transient)\n"); + pair_type = PAIR_CLIENT_HOMEKIT_TRANSIENT; + endpoint_setup = ENDPOINT_SETUP_HOMEKIT; + content_type_setup = CONTENTTYPE_SETUP_HOMEKIT; + } + + evbase = event_base_new(); + evcon = evrtsp_connection_new(address, atoi(port)); + evrtsp_connection_set_base(evcon, evbase); + + if (pair_type == PAIR_CLIENT_HOMEKIT_TRANSIENT || skip_pin) + { + setup_start_response(NULL, NULL); + } + else + { + ret = setup_start_request(); + if (ret < 0) + goto the_end; + } + + event_base_dispatch(evbase); + + the_end: + evrtsp_connection_free(evcon); + event_base_free(evbase); + + return 0; +} diff --git a/pair_ap/pair-internal.h b/pair_ap/pair-internal.h new file mode 100644 index 00000000..e7cd85e3 --- /dev/null +++ b/pair_ap/pair-internal.h @@ -0,0 +1,381 @@ +#include +#include +#include + +#include "pair.h" + +#define RETURN_ERROR(s, m) \ + do { handle->status = (s); handle->errmsg = (m); goto error; } while(0) + + +struct SRPUser; +struct SRPVerifier; + +struct pair_client_setup_context +{ + struct SRPUser *user; + + uint8_t pin[4]; + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + + pair_cb add_cb; + void *add_cb_arg; + + uint8_t public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + + const uint8_t *pkA; + int pkA_len; + + uint8_t *pkB; + uint64_t pkB_len; + + const uint8_t *M1; + int M1_len; + + uint8_t *M2; + uint64_t M2_len; + + uint8_t *salt; + uint64_t salt_len; + + // We don't actually use the server's epk and authtag for anything + uint8_t *epk; + uint64_t epk_len; + uint8_t *authtag; + uint64_t authtag_len; +}; + +struct pair_server_setup_context +{ + struct SRPVerifier *verifier; + + uint8_t pin[4]; + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + + pair_cb add_cb; + void *add_cb_arg; + + uint8_t public_key[crypto_sign_PUBLICKEYBYTES]; + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + + bool is_transient; + + uint8_t *pkA; + uint64_t pkA_len; + + uint8_t *pkB; + int pkB_len; + + uint8_t *b; + int b_len; + + uint8_t *M1; + uint64_t M1_len; + + const uint8_t *M2; + int M2_len; + + uint8_t *v; + int v_len; + + uint8_t *salt; + int salt_len; +}; + +enum pair_status +{ + PAIR_STATUS_IN_PROGRESS, + PAIR_STATUS_COMPLETED, + PAIR_STATUS_AUTH_FAILED, + PAIR_STATUS_INVALID, +}; + +struct pair_setup_context +{ + struct pair_definition *type; + + enum pair_status status; + const char *errmsg; + + struct pair_result result; + char result_str[256]; // Holds the hex string version of the keys that pair_verify_new() needs + + // Hex-formatet concatenation of public + private, 0-terminated + char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1]; + + union pair_setup_union + { + struct pair_client_setup_context client; + struct pair_server_setup_context server; + } sctx; +}; + +struct pair_client_verify_context +{ + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + + // These are the keys that were registered with the server in pair-setup + uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; // 32 + uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; // 64 + + bool verify_server_signature; + uint8_t server_fruit_public_key[64]; // Not sure why it has this length in fruit mode + uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32 + + // For establishing the shared secret for encrypted communication + uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32 + uint8_t client_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32 + + uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32 + + uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32 +}; + +struct pair_server_verify_context +{ + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + + // Same keys as used for pair-setup, derived from device_id + uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32 + uint8_t server_private_key[crypto_sign_SECRETKEYBYTES]; // 64 + + bool verify_client_signature; + pair_cb get_cb; + void *get_cb_arg; + + // For establishing the shared secret for encrypted communication + uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32 + uint8_t server_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32 + + uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32 + + uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32 +}; + +struct pair_verify_context +{ + struct pair_definition *type; + + enum pair_status status; + const char *errmsg; + + struct pair_result result; + + union pair_verify_union + { + struct pair_client_verify_context client; + struct pair_server_verify_context server; + } vctx; +}; + +struct pair_cipher_context +{ + struct pair_definition *type; + + uint8_t encryption_key[32]; + uint8_t decryption_key[32]; + + uint64_t encryption_counter; + uint64_t decryption_counter; + + // For rollback + uint64_t encryption_counter_prev; + uint64_t decryption_counter_prev; + + const char *errmsg; +}; + +struct pair_definition +{ + int (*pair_setup_new)(struct pair_setup_context *sctx, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id); + void (*pair_setup_free)(struct pair_setup_context *sctx); + int (*pair_setup_result)(struct pair_setup_context *sctx); + + 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); + + int (*pair_verify_new)(struct pair_verify_context *vctx, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id); + void (*pair_verify_free)(struct pair_verify_context *vctx); + int (*pair_verify_result)(struct pair_verify_context *vctx); + + 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); + + int (*pair_add)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len); + int (*pair_remove)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len); + int (*pair_list)(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len); + + struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len); + void (*pair_cipher_free)(struct pair_cipher_context *cctx); + + ssize_t (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx); + ssize_t (*pair_decrypt)(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx); + + int (*pair_state_get)(const char **errmsg, const uint8_t *in, size_t in_len); + void (*pair_public_key_get)(uint8_t server_public_key[32], const char *device_id); +}; + + +/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */ +/* partly borrowed from ffmpeg (rtmpdh.c) */ + +#if CONFIG_GCRYPT +#include +#define SHA512_DIGEST_LENGTH 64 +#define bnum_new(bn) \ + do { \ + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { \ + if (!gcry_check_version("1.5.4")) \ + abort(); \ + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); \ + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); \ + } \ + bn = gcry_mpi_new(1); \ + } while (0) +#define bnum_free(bn) gcry_mpi_release(bn) +#define bnum_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8 +#define bnum_is_zero(bn) (gcry_mpi_cmp_ui(bn, (unsigned long)0) == 0) +#define bnum_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn) +#define bnum_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL) +#define bnum_hex2bn(bn, buf) gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0) +#define bnum_random(bn, num_bits) gcry_mpi_randomize(bn, num_bits, GCRY_WEAK_RANDOM) +#define bnum_add(bn, a, b) gcry_mpi_add(bn, a, b) +#define bnum_sub(bn, a, b) gcry_mpi_sub(bn, a, b) +#define bnum_mul(bn, a, b) gcry_mpi_mul(bn, a, b) +#define bnum_mod(bn, a, b) gcry_mpi_mod(bn, a, b) +typedef gcry_mpi_t bnum; +__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + gcry_mpi_powm(bn, y, q, p); +} +__attribute__((unused)) static void bnum_modadd(bnum bn, bnum a, bnum b, bnum m) +{ + gcry_mpi_addm(bn, a, b, m); +} +#elif CONFIG_OPENSSL +#include +#include +#include +#include +#include +#define bnum_new(bn) bn = BN_new() +#define bnum_free(bn) BN_free(bn) +#define bnum_num_bytes(bn) BN_num_bytes(bn) +#define bnum_is_zero(bn) BN_is_zero(bn) +#define bnum_bn2bin(bn, buf, len) BN_bn2bin(bn, buf) +#define bnum_bin2bn(bn, buf, len) bn = BN_bin2bn(buf, len, 0) +#define bnum_hex2bn(bn, buf) BN_hex2bn(&bn, buf) +#define bnum_random(bn, num_bits) BN_rand(bn, num_bits, 0, 0) +#define bnum_add(bn, a, b) BN_add(bn, a, b) +#define bnum_sub(bn, a, b) BN_sub(bn, a, b) +typedef BIGNUM* bnum; +__attribute__((unused)) static void bnum_mul(bnum bn, bnum a, bnum b) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mul(bn, a, b, ctx); + BN_CTX_free(ctx); +} +__attribute__((unused)) static void bnum_mod(bnum bn, bnum a, bnum b) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mod(bn, a, b, ctx); + BN_CTX_free(ctx); +} +__attribute__((unused)) static void bnum_modexp(bnum bn, bnum y, bnum q, bnum p) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mod_exp(bn, y, q, p, ctx); + BN_CTX_free(ctx); +} +__attribute__((unused)) static void bnum_modadd(bnum bn, bnum a, bnum b, bnum m) +{ + // No error handling + BN_CTX *ctx = BN_CTX_new(); + BN_mod_add(bn, a, b, m, ctx); + BN_CTX_free(ctx); +} +#endif + + +/* -------------------------- SHARED HASHING HELPERS ------------------------ */ + +#ifdef CONFIG_OPENSSL +enum hash_alg +{ + HASH_SHA1, + HASH_SHA224, + HASH_SHA256, + HASH_SHA384, + HASH_SHA512, +}; +#elif CONFIG_GCRYPT +enum hash_alg +{ + HASH_SHA1 = GCRY_MD_SHA1, + HASH_SHA224 = GCRY_MD_SHA224, + HASH_SHA256 = GCRY_MD_SHA256, + HASH_SHA384 = GCRY_MD_SHA384, + HASH_SHA512 = GCRY_MD_SHA512, +}; +#endif + +#if CONFIG_OPENSSL +typedef union +{ + SHA_CTX sha; + SHA256_CTX sha256; + SHA512_CTX sha512; +} HashCTX; +#elif CONFIG_GCRYPT +typedef gcry_md_hd_t HashCTX; +#endif + +int +hash_init(enum hash_alg alg, HashCTX *c); + +int +hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len); + +int +hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md); + +unsigned char * +hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md); + +int +hash_length(enum hash_alg alg); + +int +hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len); + +bnum +H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2); + +bnum +H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes); + +void +update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n); + +void +hash_num(enum hash_alg alg, const bnum n, unsigned char *dest); + + +/* ----------------------------- OTHER HELPERS -------------------------------*/ + +#ifdef DEBUG_PAIR +void +hexdump(const char *msg, uint8_t *mem, size_t len); +#endif diff --git a/pair_ap/pair-tlv.c b/pair_ap/pair-tlv.c new file mode 100644 index 00000000..ea1a7614 --- /dev/null +++ b/pair_ap/pair-tlv.c @@ -0,0 +1,218 @@ +/* + * TLV helpers are adapted from ESP homekit: + * + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include +#include + +#include "pair-tlv.h" +#include "pair-internal.h" + + +static int +tlv_add_value_(pair_tlv_values_t *values, uint8_t type, uint8_t *value, size_t size) { + pair_tlv_t *tlv = malloc(sizeof(pair_tlv_t)); + if (!tlv) { + return PAIR_TLV_ERROR_MEMORY; + } + tlv->type = type; + tlv->size = size; + tlv->value = value; + tlv->next = NULL; + + if (!values->head) { + values->head = tlv; + } else { + pair_tlv_t *t = values->head; + while (t->next) { + t = t->next; + } + t->next = tlv; + } + + return 0; +} + + +pair_tlv_values_t * +pair_tlv_new() { + pair_tlv_values_t *values = malloc(sizeof(pair_tlv_values_t)); + if (!values) + return NULL; + + values->head = NULL; + return values; +} + +void +pair_tlv_free(pair_tlv_values_t *values) { + pair_tlv_t *t = values->head; + while (t) { + pair_tlv_t *t2 = t; + t = t->next; + if (t2->value) + free(t2->value); + free(t2); + } + free(values); +} + +int +pair_tlv_add_value(pair_tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size) { + uint8_t *data = NULL; + int ret; + if (size) { + data = malloc(size); + if (!data) { + return PAIR_TLV_ERROR_MEMORY; + } + memcpy(data, value, size); + } + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) + free(data); + return ret; +} + +pair_tlv_t * +pair_tlv_get_value(const pair_tlv_values_t *values, uint8_t type) { + pair_tlv_t *t = values->head; + while (t) { + if (t->type == type) + return t; + t = t->next; + } + return NULL; +} + +int +pair_tlv_format(const pair_tlv_values_t *values, uint8_t *buffer, size_t *size) { + size_t required_size = 0; + pair_tlv_t *t = values->head; + while (t) { + required_size += t->size + 2 * ((t->size + 254) / 255); + t = t->next; + } + + if (*size < required_size) { + *size = required_size; + return PAIR_TLV_ERROR_INSUFFICIENT_SIZE; + } + + *size = required_size; + + t = values->head; + while (t) { + uint8_t *data = t->value; + if (!t->size) { + buffer[0] = t->type; + buffer[1] = 0; + buffer += 2; + t = t->next; + continue; + } + + size_t remaining = t->size; + + while (remaining) { + buffer[0] = t->type; + size_t chunk_size = (remaining > 255) ? 255 : remaining; + buffer[1] = chunk_size; + memcpy(&buffer[2], data, chunk_size); + remaining -= chunk_size; + buffer += chunk_size + 2; + data += chunk_size; + } + + t = t->next; + } + + return 0; +} + +int +pair_tlv_parse(const uint8_t *buffer, size_t length, pair_tlv_values_t *values) { + size_t i = 0; + int ret; + while (i < length) { + uint8_t type = buffer[i]; + size_t size = 0; + uint8_t *data = NULL; + + // scan TLVs to accumulate total size of subsequent TLVs with same type (chunked data) + size_t j = i; + while (j < length && buffer[j] == type && buffer[j+1] == 255) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + j += chunk_size + 2; + } + if (j < length && buffer[j] == type) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + } + + // allocate memory to hold all pieces of chunked data and copy data there + if (size != 0) { + data = malloc(size); + if (!data) + return PAIR_TLV_ERROR_MEMORY; + + uint8_t *p = data; + + size_t remaining = size; + while (remaining) { + size_t chunk_size = buffer[i+1]; + memcpy(p, &buffer[i+2], chunk_size); + p += chunk_size; + i += chunk_size + 2; + remaining -= chunk_size; + } + } + + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) { + free(data); + return ret; + } + } + + return 0; +} + +#ifdef DEBUG_PAIR +void +pair_tlv_debug(const pair_tlv_values_t *values) +{ + printf("Received TLV values\n"); + for (pair_tlv_t *t=values->head; t; t=t->next) + { + printf("Type %d value (%zu bytes): \n", t->type, t->size); + hexdump("", t->value, t->size); + } +} +#endif + diff --git a/pair_ap/pair-tlv.h b/pair_ap/pair-tlv.h new file mode 100644 index 00000000..6ef8db20 --- /dev/null +++ b/pair_ap/pair-tlv.h @@ -0,0 +1,77 @@ +#ifndef __PAIR_AP_TLV_H__ +#define __PAIR_AP_TLV_H__ + +#include + +#define PAIR_TLV_ERROR_MEMORY -1 +#define PAIR_TLV_ERROR_INSUFFICIENT_SIZE -2 + +typedef enum { + TLVType_Method = 0, // (integer) Method to use for pairing. See PairMethod + TLVType_Identifier = 1, // (UTF-8) Identifier for authentication + TLVType_Salt = 2, // (bytes) 16+ bytes of random salt + TLVType_PublicKey = 3, // (bytes) Curve25519, SRP public key or signed Ed25519 key + TLVType_Proof = 4, // (bytes) Ed25519 or SRP proof + TLVType_EncryptedData = 5, // (bytes) Encrypted data with auth tag at end + TLVType_State = 6, // (integer) State of the pairing process. 1=M1, 2=M2, etc. + TLVType_Error = 7, // (integer) Error code. Must only be present if error code is + // not 0. See TLVError + TLVType_RetryDelay = 8, // (integer) Seconds to delay until retrying a setup code + TLVType_Certificate = 9, // (bytes) X.509 Certificate + TLVType_Signature = 10, // (bytes) Ed25519 + TLVType_Permissions = 11, // (integer) Bit value describing permissions of the controller + // being added. + // None (0x00): Regular user + // Bit 1 (0x01): Admin that is able to add and remove + // pairings against the accessory + TLVType_FragmentData = 13, // (bytes) Non-last fragment of data. If length is 0, + // it's an ACK. + TLVType_FragmentLast = 14, // (bytes) Last fragment of data + TLVType_Flags = 19, // Added from airplay2_receiver + TLVType_Separator = 0xff, +} TLVType; + + +typedef enum { + TLVError_Unknown = 1, // Generic error to handle unexpected errors + TLVError_Authentication = 2, // Setup code or signature verification failed + TLVError_Backoff = 3, // Client must look at the retry delay TLV item and + // wait that many seconds before retrying + TLVError_MaxPeers = 4, // Server cannot accept any more pairings + TLVError_MaxTries = 5, // Server reached its maximum number of + // authentication attempts + TLVError_Unavailable = 6, // Server pairing method is unavailable + TLVError_Busy = 7, // Server is busy and cannot accept a pairing + // request at this time +} TLVError; + +typedef struct _tlv { + struct _tlv *next; + uint8_t type; + uint8_t *value; + size_t size; +} pair_tlv_t; + + +typedef struct { + pair_tlv_t *head; +} pair_tlv_values_t; + + +pair_tlv_values_t *pair_tlv_new(); + +void pair_tlv_free(pair_tlv_values_t *values); + +int pair_tlv_add_value(pair_tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size); + +pair_tlv_t *pair_tlv_get_value(const pair_tlv_values_t *values, uint8_t type); + +int pair_tlv_format(const pair_tlv_values_t *values, uint8_t *buffer, size_t *size); + +int pair_tlv_parse(const uint8_t *buffer, size_t length, pair_tlv_values_t *values); + +#ifdef DEBUG_PAIR +void pair_tlv_debug(const pair_tlv_values_t *values); +#endif + +#endif /* !__PAIR_AP_TLV_H__ */ diff --git a/pair_ap/pair.c b/pair_ap/pair.c new file mode 100644 index 00000000..6908e8bd --- /dev/null +++ b/pair_ap/pair.c @@ -0,0 +1,771 @@ +/* + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include +#include // for isprint() + +#include + +#include "pair.h" +#include "pair-internal.h" + +extern struct pair_definition pair_client_fruit; +extern struct pair_definition pair_client_homekit_normal; +extern struct pair_definition pair_client_homekit_transient; +extern struct pair_definition pair_server_homekit; + +// Must be in sync with enum pair_type +static struct pair_definition *pair[] = { + &pair_client_fruit, + &pair_client_homekit_normal, + &pair_client_homekit_transient, + &pair_server_homekit, +}; + +/* -------------------------- SHARED HASHING HELPERS ------------------------ */ + +int +hash_init(enum hash_alg alg, HashCTX *c) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Init(&c->sha); + case HASH_SHA224: return SHA224_Init(&c->sha256); + case HASH_SHA256: return SHA256_Init(&c->sha256); + case HASH_SHA384: return SHA384_Init(&c->sha512); + case HASH_SHA512: return SHA512_Init(&c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + gcry_error_t err; + + err = gcry_md_open(c, alg, 0); + + if (err) + return -1; + + return 0; +#endif +} + +int +hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Update(&c->sha, data, len); + case HASH_SHA224: return SHA224_Update(&c->sha256, data, len); + case HASH_SHA256: return SHA256_Update(&c->sha256, data, len); + case HASH_SHA384: return SHA384_Update(&c->sha512, data, len); + case HASH_SHA512: return SHA512_Update(&c->sha512, data, len); + default: + return -1; + }; +#elif CONFIG_GCRYPT + gcry_md_write(*c, data, len); + return 0; +#endif +} + +int +hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1_Final(md, &c->sha); + case HASH_SHA224: return SHA224_Final(md, &c->sha256); + case HASH_SHA256: return SHA256_Final(md, &c->sha256); + case HASH_SHA384: return SHA384_Final(md, &c->sha512); + case HASH_SHA512: return SHA512_Final(md, &c->sha512); + default: + return -1; + }; +#elif CONFIG_GCRYPT + unsigned char *buf = gcry_md_read(*c, alg); + if (!buf) + return -1; + + memcpy(md, buf, gcry_md_get_algo_dlen(alg)); + gcry_md_close(*c); + return 0; +#endif +} + +unsigned char * +hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA1(d, n, md); + case HASH_SHA224: return SHA224(d, n, md); + case HASH_SHA256: return SHA256(d, n, md); + case HASH_SHA384: return SHA384(d, n, md); + case HASH_SHA512: return SHA512(d, n, md); + default: + return NULL; + }; +#elif CONFIG_GCRYPT + gcry_md_hash_buffer(alg, md, d, n); + return md; +#endif +} + +int +hash_length(enum hash_alg alg) +{ +#if CONFIG_OPENSSL + switch (alg) + { + case HASH_SHA1 : return SHA_DIGEST_LENGTH; + case HASH_SHA224: return SHA224_DIGEST_LENGTH; + case HASH_SHA256: return SHA256_DIGEST_LENGTH; + case HASH_SHA384: return SHA384_DIGEST_LENGTH; + case HASH_SHA512: return SHA512_DIGEST_LENGTH; + default: + return -1; + }; +#elif CONFIG_GCRYPT + return gcry_md_get_algo_dlen(alg); +#endif +} + +int +hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len) +{ + HashCTX ctx; + + hash_init(alg, &ctx); + hash_update(alg, &ctx, m1, m1_len); + hash_update(alg, &ctx, m2, m2_len); + return hash_final(alg, &ctx, md); +} + +bnum +H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) +{ + bnum bn; + unsigned char *bin; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n1 = bnum_num_bytes(n1); + int len_n2 = bnum_num_bytes(n2); + int nbytes = 2 * len_n1; + + if ((len_n2 < 1) || (len_n2 > len_n1)) + return 0; + + bin = calloc( 1, nbytes ); + + bnum_bn2bin(n1, bin, len_n1); + bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +bnum +H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes) +{ + bnum bn; + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n = bnum_num_bytes(n); + int nbytes = len_n + len_bytes; + unsigned char *bin = malloc(nbytes); + + bnum_bn2bin(n, bin, len_n); + memcpy( bin + len_n, bytes, len_bytes ); + hash( alg, bin, nbytes, buff ); + free(bin); + bnum_bin2bn(bn, buff, hash_length(alg)); + return bn; +} + +void +update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n) +{ + unsigned long len = bnum_num_bytes(n); + unsigned char *n_bytes = malloc(len); + + bnum_bn2bin(n, n_bytes, len); + hash_update(alg, ctx, n_bytes, len); + free(n_bytes); +} + +void +hash_num(enum hash_alg alg, const bnum n, unsigned char *dest) +{ + int nbytes = bnum_num_bytes(n); + unsigned char *bin = malloc(nbytes); + + bnum_bn2bin(n, bin, nbytes); + hash( alg, bin, nbytes, dest ); + free(bin); +} + + +/* ----------------------------- OTHER HELPERS -------------------------------*/ + +#ifdef DEBUG_PAIR +void +hexdump(const char *msg, uint8_t *mem, size_t len) +{ + int i, j; + int hexdump_cols = 16; + + if (msg) + printf("%s", msg); + + for (i = 0; i < len + ((len % hexdump_cols) ? (hexdump_cols - len % hexdump_cols) : 0); i++) + { + if(i % hexdump_cols == 0) + printf("0x%06x: ", i); + + if (i < len) + printf("%02x ", 0xFF & ((char*)mem)[i]); + else + printf(" "); + + if (i % hexdump_cols == (hexdump_cols - 1)) + { + for (j = i - (hexdump_cols - 1); j <= i; j++) + { + if (j >= len) + putchar(' '); + else if (isprint(((char*)mem)[j])) + putchar(0xFF & ((char*)mem)[j]); + else + putchar('.'); + } + + putchar('\n'); + } + } +} +#endif + + +/* ----------------------------------- API -----------------------------------*/ + +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) +{ + struct pair_setup_context *sctx; + + if (!pair[type]->pair_setup_new) + return NULL; + + sctx = calloc(1, sizeof(struct pair_setup_context)); + if (!sctx) + return NULL; + + sctx->type = pair[type]; + + if (pair[type]->pair_setup_new(sctx, pin, add_cb, cb_arg, device_id) < 0) + { + free(sctx); + return NULL; + } + + return sctx; +} + +void +pair_setup_free(struct pair_setup_context *sctx) +{ + if (!sctx) + return; + + if (sctx->type->pair_setup_free) + sctx->type->pair_setup_free(sctx); + + free(sctx); +} + +const char * +pair_setup_errmsg(struct pair_setup_context *sctx) +{ + return sctx->errmsg; +} + +int +pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len) +{ + int state; + int ret = -1; + + if (!sctx->type->pair_state_get) + { + sctx->errmsg = "Getting pair state unsupported"; + return -1; + } + + *out = NULL; + *out_len = 0; + + state = sctx->type->pair_state_get(&sctx->errmsg, in, in_len); + if (state < 0) + return -1; + + switch (state) + { + case 0: + *out = pair_setup_request1(out_len, sctx); + break; + case 1: + ret = pair_setup_response1(sctx, in, in_len); + if (ret < 0) + break; + *out = pair_setup_request1(out_len, sctx); + break; + case 2: + ret = pair_setup_response1(sctx, in, in_len); + if (ret < 0) + break; + *out = pair_setup_request2(out_len, sctx); + break; + case 3: + ret = pair_setup_response2(sctx, in, in_len); + if (ret < 0) + break; + *out = pair_setup_request2(out_len, sctx); + break; + case 4: + ret = pair_setup_response2(sctx, in, in_len); + if (ret < 0) + break; + *out = pair_setup_request3(out_len, sctx); + break; + case 5: + ret = pair_setup_response3(sctx, in, in_len); + if (ret < 0) + break; + *out = pair_setup_request3(out_len, sctx); + break; + case 6: + ret = pair_setup_response3(sctx, in, in_len); + if (ret < 0) + break; + break; + default: + sctx->errmsg = "Setup: Unsupported state"; + ret = -1; + } + + if (ret < 0 || !(*out)) + return -1; + + return 0; +} + +uint8_t * +pair_setup_request1(size_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request1) + { + sctx->errmsg = "Setup request 1: Unsupported"; + return NULL; + } + + return sctx->type->pair_setup_request1(len, sctx); +} + +uint8_t * +pair_setup_request2(size_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request2) + { + sctx->errmsg = "Setup request 2: Unsupported"; + return NULL; + } + + return sctx->type->pair_setup_request2(len, sctx); +} + +uint8_t * +pair_setup_request3(size_t *len, struct pair_setup_context *sctx) +{ + if (!sctx->type->pair_setup_request3) + { + sctx->errmsg = "Setup request 3: Unsupported"; + return NULL; + } + + return sctx->type->pair_setup_request3(len, sctx); +} + +int +pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len) +{ + if (!sctx->type->pair_setup_response1) + { + sctx->errmsg = "Setup response 1: Unsupported"; + return -1; + } + + return sctx->type->pair_setup_response1(sctx, in, in_len); +} + +int +pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len) +{ + if (!sctx->type->pair_setup_response2) + { + sctx->errmsg = "Setup response 2: Unsupported"; + return -1; + } + + return sctx->type->pair_setup_response2(sctx, in, in_len); +} + +int +pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len) +{ + if (!sctx->type->pair_setup_response3) + { + sctx->errmsg = "Setup response 3: Unsupported"; + return -1; + } + + if (sctx->type->pair_setup_response3(sctx, in, in_len) != 0) + return -1; + + return 0; +} + +int +pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx) +{ + if (sctx->status != PAIR_STATUS_COMPLETED) + { + sctx->errmsg = "Setup result: Pair setup has not been completed"; + return -1; + } + + if (sctx->type->pair_setup_result) + { + if (sctx->type->pair_setup_result(sctx) != 0) + return -1; + } + + if (client_setup_keys) + *client_setup_keys = sctx->result_str; + if (result) + *result = &sctx->result; + return 0; +} + +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) +{ + struct pair_verify_context *vctx; + + if (!pair[type]->pair_verify_new) + return NULL; + + vctx = calloc(1, sizeof(struct pair_verify_context)); + if (!vctx) + return NULL; + + vctx->type = pair[type]; + + if (pair[type]->pair_verify_new(vctx, client_setup_keys, get_cb, cb_arg, device_id) < 0) + { + free(vctx); + return NULL; + } + + return vctx; +} + +void +pair_verify_free(struct pair_verify_context *vctx) +{ + if (!vctx) + return; + + if (vctx->type->pair_verify_free) + vctx->type->pair_verify_free(vctx); + + free(vctx); +} + +const char * +pair_verify_errmsg(struct pair_verify_context *vctx) +{ + return vctx->errmsg; +} + +int +pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *vctx, const uint8_t *in, size_t in_len) +{ + int state; + int ret = -1; + + if (!vctx->type->pair_state_get) + { + vctx->errmsg = "Getting pair state unsupported"; + return -1; + } + + *out = NULL; + *out_len = 0; + + state = vctx->type->pair_state_get(&vctx->errmsg, in, in_len); + if (state < 0) + return -1; + + switch (state) + { + case 0: + *out = pair_verify_request1(out_len, vctx); + break; + case 1: + ret = pair_verify_response1(vctx, in, in_len); + if (ret < 0) + break; + *out = pair_verify_request1(out_len, vctx); + break; + case 2: + ret = pair_verify_response1(vctx, in, in_len); + if (ret < 0) + break; + *out = pair_verify_request2(out_len, vctx); + break; + case 3: + ret = pair_verify_response2(vctx, in, in_len); + if (ret < 0) + break; + *out = pair_verify_request2(out_len, vctx); + break; + case 4: + ret = pair_verify_response2(vctx, in, in_len); + if (ret < 0) + break; + break; + default: + vctx->errmsg = "Verify: Unsupported state"; + ret = -1; + } + + if (ret < 0 || !(*out)) + return -1; + + return 0; +} + +uint8_t * +pair_verify_request1(size_t *len, struct pair_verify_context *vctx) +{ + if (!vctx->type->pair_verify_request1) + { + vctx->errmsg = "Verify request 1: Unsupported"; + return NULL; + } + + return vctx->type->pair_verify_request1(len, vctx); +} + +uint8_t * +pair_verify_request2(size_t *len, struct pair_verify_context *vctx) +{ + if (!vctx->type->pair_verify_request2) + { + vctx->errmsg = "Verify request 2: Unsupported"; + return NULL; + } + + return vctx->type->pair_verify_request2(len, vctx); +} + +int +pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len) +{ + if (!vctx->type->pair_verify_response1) + { + vctx->errmsg = "Verify response 1: Unsupported"; + return -1; + } + + return vctx->type->pair_verify_response1(vctx, in, in_len); +} + +int +pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len) +{ + if (!vctx->type->pair_verify_response2) + { + vctx->errmsg = "Verify response 2: Unsupported"; + return -1; + } + + if (vctx->type->pair_verify_response2(vctx, in, in_len) != 0) + return -1; + + return 0; +} + +int +pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx) +{ + if (vctx->status != PAIR_STATUS_COMPLETED) + { + vctx->errmsg = "Verify result: The pairing verification did not complete"; + return -1; + } + + if (vctx->type->pair_verify_result) + { + if (vctx->type->pair_verify_result(vctx) != 0) + return -1; + } + + if (result) + *result = &vctx->result; + return 0; +} + +struct pair_cipher_context * +pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len) +{ + if (!pair[type]->pair_cipher_new) + return NULL; + + return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len); +} + +void +pair_cipher_free(struct pair_cipher_context *cctx) +{ + if (!cctx) + return; + + if (!cctx->type->pair_cipher_free) + return; + + return cctx->type->pair_cipher_free(cctx); +} + +const char * +pair_cipher_errmsg(struct pair_cipher_context *cctx) +{ + return cctx->errmsg; +} + +ssize_t +pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) +{ + if (!cctx->type->pair_encrypt) + { + cctx->errmsg = "Encryption unsupported"; + return -1; + } + + return cctx->type->pair_encrypt(ciphertext, ciphertext_len, plaintext, plaintext_len, cctx); +} + +ssize_t +pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) +{ + if (!cctx->type->pair_decrypt) + { + cctx->errmsg = "Decryption unsupported"; + return -1; + } + + return cctx->type->pair_decrypt(plaintext, plaintext_len, ciphertext, ciphertext_len, cctx); +} + +void +pair_encrypt_rollback(struct pair_cipher_context *cctx) +{ + cctx->encryption_counter = cctx->encryption_counter_prev; +} + +void +pair_decrypt_rollback(struct pair_cipher_context *cctx) +{ + cctx->decryption_counter = cctx->decryption_counter_prev; +} + +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) +{ + if (!pair[type]->pair_add) + { + return -1; + } + + return pair[type]->pair_add(out, out_len, add_cb, cb_arg, in, 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) +{ + if (!pair[type]->pair_remove) + { + return -1; + } + + return pair[type]->pair_remove(out, out_len, remove_cb, cb_arg, in, 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) +{ + if (!pair[type]->pair_list) + { + return -1; + } + + return pair[type]->pair_list(out, out_len, list_cb, cb_arg, in, in_len); +} + +int +pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len) +{ + if (!pair[type]->pair_state_get) + { + *errmsg = "Getting pair state unsupported"; + return -1; + } + + return pair[type]->pair_state_get(errmsg, in, in_len); +} + +void +pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id) +{ + if (!pair[type]->pair_public_key_get) + { + return; + } + + pair[type]->pair_public_key_get(server_public_key, device_id); +} diff --git a/pair_ap/pair.h b/pair_ap/pair.h new file mode 100644 index 00000000..6324bc15 --- /dev/null +++ b/pair_ap/pair.h @@ -0,0 +1,266 @@ +#ifndef __PAIR_AP_H__ +#define __PAIR_AP_H__ + +#include + +#define PAIR_AP_VERSION_MAJOR 0 +#define PAIR_AP_VERSION_MINOR 5 + +#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__ */ diff --git a/pair_ap/pair_ap-master/.gitignore b/pair_ap/pair_ap-master/.gitignore new file mode 100644 index 00000000..a7eeb67d --- /dev/null +++ b/pair_ap/pair_ap-master/.gitignore @@ -0,0 +1,10 @@ +*.o +*.lo +*.a +*.la +.dirstamp +.deps/ +.libs/ + +client-example +server-example diff --git a/pair_ap/pair_ap-master/evrtsp/evrtsp.h b/pair_ap/pair_ap-master/evrtsp/evrtsp.h new file mode 100644 index 00000000..5a03cd6d --- /dev/null +++ b/pair_ap/pair_ap-master/evrtsp/evrtsp.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2010 Julien BLACHE + * Based on evhttp from libevent 1.4.x + * + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _EVRTSP_H_ +#define _EVRTSP_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#undef WIN32_LEAN_AND_MEAN +#endif + +/* Response codes */ +#define RTSP_OK 200 +#define RTSP_UNAUTHORIZED 401 +#define RTSP_FORBIDDEN 403 + +struct evrtsp_connection; + +/* + * Interfaces for making requests + */ +enum evrtsp_cmd_type { + EVRTSP_REQ_ANNOUNCE, + EVRTSP_REQ_OPTIONS, + EVRTSP_REQ_SETUP, + EVRTSP_REQ_RECORD, + EVRTSP_REQ_PAUSE, + EVRTSP_REQ_GET_PARAMETER, // Careful using this, some devices do not support it or will hang up eg RAOP_DEV_APEX1_80211G + EVRTSP_REQ_SET_PARAMETER, + EVRTSP_REQ_FLUSH, + EVRTSP_REQ_TEARDOWN, + EVRTSP_REQ_POST, +}; + +enum evrtsp_request_kind { EVRTSP_REQUEST, EVRTSP_RESPONSE }; + +struct evrtsp_request { +#if defined(TAILQ_ENTRY) + TAILQ_ENTRY(evrtsp_request) next; +#else +struct { + struct evrtsp_request *tqe_next; + struct evrtsp_request **tqe_prev; +} next; +#endif + + /* the connection object that this request belongs to */ + struct evrtsp_connection *evcon; + int flags; +#define EVRTSP_REQ_OWN_CONNECTION 0x0001 + + struct evkeyvalq *input_headers; + struct evkeyvalq *output_headers; + + enum evrtsp_request_kind kind; + enum evrtsp_cmd_type type; + + char *uri; /* uri after RTSP request was parsed */ + + char major; /* RTSP Major number */ + char minor; /* RTSP Minor number */ + + int response_code; /* RTSP Response code */ + char *response_code_line; /* Readable response */ + + struct evbuffer *input_buffer; /* read data */ + ev_int64_t ntoread; + + struct evbuffer *output_buffer; /* outgoing post or data */ + + /* Callback */ + void (*cb)(struct evrtsp_request *, void *); + void *cb_arg; +}; + +/** + * Creates a new request object that needs to be filled in with the request + * parameters. The callback is executed when the request completed or an + * error occurred. + */ +struct evrtsp_request *evrtsp_request_new( + void (*cb)(struct evrtsp_request *, void *), void *arg); + +/** Frees the request object and removes associated events. */ +void evrtsp_request_free(struct evrtsp_request *req); + +/** + * A connection object that can be used to for making RTSP requests. The + * connection object tries to establish the connection when it is given an + * rtsp request object. + */ +struct evrtsp_connection *evrtsp_connection_new( + const char *address, unsigned short port); + +/** Frees an rtsp connection */ +void evrtsp_connection_free(struct evrtsp_connection *evcon); + +/** Set a callback for connection close. */ +void evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, + void (*)(struct evrtsp_connection *, void *), void *); + +/** Set a callback for encryption/decryption. */ +void evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, + void (*)(struct evbuffer *, void *, int encrypt), void *); + +/** + * Associates an event base with the connection - can only be called + * on a freshly created connection object that has not been used yet. + */ +void evrtsp_connection_set_base(struct evrtsp_connection *evcon, + struct event_base *base); + +/** Get the remote address and port associated with this connection. */ +void evrtsp_connection_get_peer(struct evrtsp_connection *evcon, + char **address, u_short *port); + +/** Get the local address, port and family associated with this connection. */ +void +evrtsp_connection_get_local_address(struct evrtsp_connection *evcon, + char **address, u_short *port, int *family); + +/** The connection gets ownership of the request */ +int evrtsp_make_request(struct evrtsp_connection *evcon, + struct evrtsp_request *req, + enum evrtsp_cmd_type type, const char *uri); + +const char *evrtsp_request_uri(struct evrtsp_request *req); + +/* Interfaces for dealing with headers */ + +const char *evrtsp_find_header(const struct evkeyvalq *, const char *); +int evrtsp_remove_header(struct evkeyvalq *, const char *); +int evrtsp_add_header(struct evkeyvalq *, const char *, const char *); +void evrtsp_clear_headers(struct evkeyvalq *); + +const char *evrtsp_method(enum evrtsp_cmd_type type); + +#ifdef __cplusplus +} +#endif + +#endif /* !_EVRTSP_H_ */ diff --git a/pair_ap/pair_ap-master/evrtsp/log.h b/pair_ap/pair_ap-master/evrtsp/log.h new file mode 100644 index 00000000..7bc6632b --- /dev/null +++ b/pair_ap/pair_ap-master/evrtsp/log.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _LOG_H_ +#define _LOG_H_ + +#ifdef __GNUC__ +#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b))) +#else +#define EV_CHECK_FMT(a,b) +#endif + +void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3); +void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2); +void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2); + +#ifdef USE_DEBUG +#define event_debug(x) _event_debugx x +#else +#define event_debug(x) do {;} while (0) +#endif + +#undef EV_CHECK_FMT + +#endif diff --git a/pair_ap/pair_ap-master/evrtsp/rtsp-internal.h b/pair_ap/pair_ap-master/evrtsp/rtsp-internal.h new file mode 100644 index 00000000..dcab48a1 --- /dev/null +++ b/pair_ap/pair_ap-master/evrtsp/rtsp-internal.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 Julien BLACHE + * Based on evhttp from libevent 1.4.x + * + * Copyright 2001 Niels Provos + * All rights reserved. + * + * This header file contains definitions for dealing with RTSP requests + * that are internal to libevent. As user of the library, you should not + * need to know about these. + */ + +#ifndef _RTSP_H_ +#define _RTSP_H_ + +#include +#include + +#define RTSP_CONNECT_TIMEOUT 5 +#define RTSP_WRITE_TIMEOUT 30 +#define RTSP_READ_TIMEOUT 30 + +#define RTSP_PREFIX "rtsp://" + +enum message_read_status { + ALL_DATA_READ = 1, + MORE_DATA_EXPECTED = 0, + DATA_CORRUPTED = -1, + REQUEST_CANCELED = -2 +}; + +enum evrtsp_connection_error { + EVCON_RTSP_TIMEOUT, + EVCON_RTSP_EOF, + EVCON_RTSP_INVALID_HEADER +}; + +struct evbuffer; +struct addrinfo; +struct evrtsp_request; + +/* A stupid connection object - maybe make this a bufferevent later */ + +enum evrtsp_connection_state { + EVCON_DISCONNECTED, /**< not currently connected not trying either*/ + EVCON_CONNECTING, /**< tries to currently connect */ + EVCON_IDLE, /**< connection is established */ + EVCON_READING_FIRSTLINE,/**< reading Request-Line (incoming conn) or + **< Status-Line (outgoing conn) */ + EVCON_READING_HEADERS, /**< reading request/response headers */ + EVCON_READING_BODY, /**< reading request/response body */ + EVCON_READING_TRAILER, /**< reading request/response chunked trailer */ + EVCON_WRITING /**< writing request/response headers/body */ +}; + +struct event_base; + +struct evrtsp_connection { + int fd; + struct event ev; + struct event close_ev; + struct evbuffer *input_buffer; + struct evbuffer *output_buffer; + + char *bind_address; /* address to use for binding the src */ + u_short bind_port; /* local port for binding the src */ + + char *address; /* address to connect to */ + int family; + u_short port; + + int flags; +#define EVRTSP_CON_CLOSEDETECT 0x0004 /* detecting if persistent close */ + + int timeout; /* timeout in seconds for events */ + + enum evrtsp_connection_state state; + int cseq; + + TAILQ_HEAD(evcon_requestq, evrtsp_request) requests; + + void (*cb)(struct evrtsp_connection *, void *); + void *cb_arg; + + void (*closecb)(struct evrtsp_connection *, void *); + void *closecb_arg; + + void (*ciphercb)(struct evbuffer *evbuf, void *, int encrypt); + void *ciphercb_arg; + + struct event_base *base; +}; + +/* resets the connection; can be reused for more requests */ +void evrtsp_connection_reset(struct evrtsp_connection *); + +/* connects if necessary */ +int evrtsp_connection_connect(struct evrtsp_connection *); + +/* notifies the current request that it failed; resets connection */ +void evrtsp_connection_fail(struct evrtsp_connection *, + enum evrtsp_connection_error error); + +int evrtsp_hostportfile(char *, char **, u_short *, char **); + +int evrtsp_parse_firstline(struct evrtsp_request *, struct evbuffer*); +int evrtsp_parse_headers(struct evrtsp_request *, struct evbuffer*); + +void evrtsp_start_read(struct evrtsp_connection *); +void evrtsp_make_header(struct evrtsp_connection *, struct evrtsp_request *); + +void evrtsp_write_buffer(struct evrtsp_connection *, + void (*)(struct evrtsp_connection *, void *), void *); + +#endif /* _RTSP_H */ diff --git a/pair_ap/pair_ap-master/evrtsp/rtsp.c b/pair_ap/pair_ap-master/evrtsp/rtsp.c new file mode 100644 index 00000000..82efc548 --- /dev/null +++ b/pair_ap/pair_ap-master/evrtsp/rtsp.c @@ -0,0 +1,1816 @@ +/* + * Copyright (C) 2010 Julien BLACHE + * Based on evhttp from libevent 1.4.x + * + * Copyright (c) 2002-2006 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#undef timeout_pending +#undef timeout_initialized + +#include "evrtsp.h" +/* #define USE_DEBUG */ +#include "log.h" +#include "rtsp-internal.h" + +// For compability with libevent 2.0 (HAVE_LIBEVENT2_OLD) +#if defined(_EVENT_HAVE_GETNAMEINFO) +# define EVENT__HAVE_GETNAMEINFO 1 +#endif +#if defined(_EVENT_HAVE_GETADDRINFO) +# define EVENT__HAVE_GETADDRINFO 1 +#endif +#if defined(_EVENT_HAVE_STRSEP) +# define EVENT__HAVE_STRSEP 1 +#endif + +#ifndef EVENT__HAVE_GETNAMEINFO +#define NI_MAXSERV 32 +#define NI_MAXHOST 1025 + +#define NI_NUMERICHOST 1 +#define NI_NUMERICSERV 2 + +static int +fake_getnameinfo(const struct sockaddr *sa, size_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + int ret; + + if (serv != NULL) { + char tmpserv[16]; + evutil_snprintf(tmpserv, sizeof(tmpserv), + "%d", ntohs(sin->sin_port)); + ret = evutil_snprintf(serv, servlen, "%s", tmpserv); + if ((ret < 0) || (ret >= servlen)) + return (-1); + } + + if (host != NULL) { + if (flags & NI_NUMERICHOST) { + ret = evutil_snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr)); + if ((ret < 0) || (ret >= hostlen)) + return (-1); + else + return (0); + } else { + struct hostent *hp; + hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hp == NULL) + return (-2); + + ret = evutil_snprintf(host, hostlen, "%s", hp->h_name); + if ((ret < 0) || (ret >= hostlen)) + return (-1); + else + return (0); + } + } + return (0); +} + +#endif + +#ifndef EVENT__HAVE_GETADDRINFO +struct addrinfo { + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + struct addrinfo *ai_next; +}; +static int +fake_getaddrinfo(const char *hostname, struct addrinfo *ai) +{ + struct hostent *he = NULL; + struct sockaddr_in *sa; + if (hostname) { + he = gethostbyname(hostname); + if (!he) + return (-1); + } + ai->ai_family = he ? he->h_addrtype : AF_INET; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = 0; + ai->ai_addrlen = sizeof(struct sockaddr_in); + if (NULL == (ai->ai_addr = malloc(ai->ai_addrlen))) + return (-1); + sa = (struct sockaddr_in*)ai->ai_addr; + memset(sa, 0, ai->ai_addrlen); + if (he) { + sa->sin_family = he->h_addrtype; + memcpy(&sa->sin_addr, he->h_addr_list[0], he->h_length); + } else { + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = INADDR_ANY; + } + ai->ai_next = NULL; + return (0); +} +static void +fake_freeaddrinfo(struct addrinfo *ai) +{ + free(ai->ai_addr); +} +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +/* wrapper for setting the base from the rtsp server */ +#define EVRTSP_BASE_SET(x, y) do { \ + if ((x)->base != NULL) event_base_set((x)->base, y); \ +} while (0) + +extern int debug; + +static int socket_connect(int fd, const char *address, unsigned short port); +static int bind_socket_ai(int family, struct addrinfo *, int reuse); +static int bind_socket(int family, const char *, u_short, int reuse); +static void name_from_addr(struct sockaddr *, socklen_t, char **, char **); +static void evrtsp_connection_start_detectclose( + struct evrtsp_connection *evcon); +static void evrtsp_connection_stop_detectclose( + struct evrtsp_connection *evcon); +static void evrtsp_request_dispatch(struct evrtsp_connection* evcon); +static void evrtsp_read_firstline(struct evrtsp_connection *evcon, + struct evrtsp_request *req); +static void evrtsp_read_header(struct evrtsp_connection *evcon, + struct evrtsp_request *req); +static int evrtsp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value); + +void evrtsp_read(int, short, void *); +void evrtsp_write(int, short, void *); + +#ifndef EVENT__HAVE_STRSEP +/* strsep replacement for platforms that lack it. Only works if + * del is one character long. */ +static char * +strsep(char **s, const char *del) +{ + char *d, *tok; + assert(strlen(del) == 1); + if (!s || !*s) + return NULL; + tok = *s; + d = strstr(tok, del); + if (d) { + *d = '\0'; + *s = d + 1; + } else + *s = NULL; + return tok; +} +#endif + +const char * +evrtsp_method(enum evrtsp_cmd_type type) +{ + const char *method; + + switch (type) { + case EVRTSP_REQ_ANNOUNCE: + method = "ANNOUNCE"; + break; + + case EVRTSP_REQ_OPTIONS: + method = "OPTIONS"; + break; + + case EVRTSP_REQ_SETUP: + method = "SETUP"; + break; + + case EVRTSP_REQ_RECORD: + method = "RECORD"; + break; + + case EVRTSP_REQ_PAUSE: + method = "PAUSE"; + break; + + case EVRTSP_REQ_GET_PARAMETER: + method = "GET_PARAMETER"; + break; + + case EVRTSP_REQ_SET_PARAMETER: + method = "SET_PARAMETER"; + break; + + case EVRTSP_REQ_FLUSH: + method = "FLUSH"; + break; + + case EVRTSP_REQ_TEARDOWN: + method = "TEARDOWN"; + break; + + case EVRTSP_REQ_POST: + method = "POST"; + break; + + default: + method = NULL; + break; + } + + return (method); +} + +static void +evrtsp_add_event(struct event *ev, int timeout, int default_timeout) +{ + if (timeout != 0) { + struct timeval tv; + + evutil_timerclear(&tv); + tv.tv_sec = timeout != -1 ? timeout : default_timeout; + event_add(ev, &tv); + } else { + event_add(ev, NULL); + } +} + +void +evrtsp_write_buffer(struct evrtsp_connection *evcon, + void (*cb)(struct evrtsp_connection *, void *), void *arg) +{ + event_debug(("%s: preparing to write buffer", __func__)); + + /* Set call back */ + evcon->cb = cb; + evcon->cb_arg = arg; + + /* check if the event is already pending */ + if (event_pending(&evcon->ev, EV_WRITE|EV_TIMEOUT, NULL)) + event_del(&evcon->ev); + + event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_write, evcon); + evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_WRITE_TIMEOUT); +} + +static int +evrtsp_connected(struct evrtsp_connection *evcon) +{ + switch (evcon->state) { + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + return (0); + case EVCON_IDLE: + case EVCON_READING_FIRSTLINE: + case EVCON_READING_HEADERS: + case EVCON_READING_BODY: + case EVCON_READING_TRAILER: + case EVCON_WRITING: + default: + return (1); + } +} + +/* + * Create the headers needed for an RTSP request + */ +static void +evrtsp_make_header_request(struct evrtsp_connection *evcon, + struct evrtsp_request *req) +{ + const char *method; + + /* Generate request line */ + method = evrtsp_method(req->type); + evbuffer_add_printf(evcon->output_buffer, "%s %s RTSP/%d.%d\r\n", + method, req->uri, req->major, req->minor); + + /* Content-Length is mandatory, absent means 0 */ + if ((evbuffer_get_length(req->output_buffer) > 0) + && (evrtsp_find_header(req->output_headers, "Content-Length") == NULL)) + { + char size[12]; + evutil_snprintf(size, sizeof(size), "%ld", + (long)evbuffer_get_length(req->output_buffer)); + evrtsp_add_header(req->output_headers, "Content-Length", size); + } +} + +void +evrtsp_make_header(struct evrtsp_connection *evcon, struct evrtsp_request *req) +{ + struct evkeyval *header; + + evrtsp_make_header_request(evcon, req); + + TAILQ_FOREACH(header, req->output_headers, next) { + evbuffer_add_printf(evcon->output_buffer, "%s: %s\r\n", + header->key, header->value); + } + evbuffer_add(evcon->output_buffer, "\r\n", 2); + + if (evbuffer_get_length(req->output_buffer) > 0) { + evbuffer_add_buffer(evcon->output_buffer, req->output_buffer); + } +} + +/* Separated host, port and file from URI */ + +int /* FIXME: needed? */ +evrtsp_hostportfile(char *url, char **phost, u_short *pport, char **pfile) +{ + /* XXX not threadsafe. */ + static char host[1024]; + static char file[1024]; + char *p; + const char *p2; + int len; + int ret; + u_short port; + + len = strlen(RTSP_PREFIX); + if (strncasecmp(url, RTSP_PREFIX, len)) + return (-1); + + url += len; + + /* We might overrun */ + ret = evutil_snprintf(host, sizeof(host), "%s", url); + if ((ret < 0) || (ret >= sizeof(host))) + return (-1); + + p = strchr(host, '/'); + if (p != NULL) { + *p = '\0'; + p2 = p + 1; + } else + p2 = NULL; + + if (pfile != NULL) { + /* Generate request file */ + if (p2 == NULL) + p2 = ""; + evutil_snprintf(file, sizeof(file), "/%s", p2); + } + + p = strchr(host, ':'); + if (p != NULL) { + *p = '\0'; + port = atoi(p + 1); + + if (port == 0) + return (-1); + } else + return -1; + + if (phost != NULL) + *phost = host; + if (pport != NULL) + *pport = port; + if (pfile != NULL) + *pfile = file; + + return (0); +} + +void +evrtsp_connection_fail(struct evrtsp_connection *evcon, + enum evrtsp_connection_error error) +{ + struct evrtsp_request* req = TAILQ_FIRST(&evcon->requests); + void (*cb)(struct evrtsp_request *, void *); + void *cb_arg; + assert(req != NULL); + + /* save the callback for later; the cb might free our object */ + cb = req->cb; + cb_arg = req->cb_arg; + + TAILQ_REMOVE(&evcon->requests, req, next); + evrtsp_request_free(req); + + /* xxx: maybe we should fail all requests??? */ + + /* reset the connection */ + evrtsp_connection_reset(evcon); + + /* We are trying the next request that was queued on us */ + if (TAILQ_FIRST(&evcon->requests) != NULL) + evrtsp_connection_connect(evcon); + + /* inform the user */ + if (cb != NULL) + (*cb)(NULL, cb_arg); +} + +void +evrtsp_write(int fd, short what, void *arg) +{ + struct evrtsp_connection *evcon = arg; + int n; + + if (what == EV_TIMEOUT) { + event_warn("%s: write timeout", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT); + return; + } + + n = evbuffer_write(evcon->output_buffer, fd); + if (n == -1) { + event_warn("%s: evbuffer_write", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); + return; + } + + if (n == 0) { + event_warn("%s: write nothing", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); + return; + } + + if (evbuffer_get_length(evcon->output_buffer) != 0) { + evrtsp_add_event(&evcon->ev, + evcon->timeout, RTSP_WRITE_TIMEOUT); + return; + } + + /* Activate our call back */ + if (evcon->cb != NULL) + (*evcon->cb)(evcon, evcon->cb_arg); +} + +/** + * Advance the connection state. + * - If this is an outgoing connection, we've just processed the response; + * idle or close the connection. + */ +static void +evrtsp_connection_done(struct evrtsp_connection *evcon) +{ + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + + /* idle or close the connection */ + TAILQ_REMOVE(&evcon->requests, req, next); + req->evcon = NULL; + + evcon->state = EVCON_IDLE; + + if (TAILQ_FIRST(&evcon->requests) != NULL) { + /* + * We have more requests; reset the connection + * and deal with the next request. + */ + if (!evrtsp_connected(evcon)) + evrtsp_connection_connect(evcon); + else + evrtsp_request_dispatch(evcon); + } else { + /* + * The connection is going to be persistent, but we + * need to detect if the other side closes it. + */ + evrtsp_connection_start_detectclose(evcon); + } + + /* notify the user of the request */ + (*req->cb)(req, req->cb_arg); + + evrtsp_request_free(req); +} + +static void /* FIXME: needed? */ +evrtsp_read_trailer(struct evrtsp_connection *evcon, struct evrtsp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + switch (evrtsp_parse_headers(req, buf)) { + case DATA_CORRUPTED: + event_warn("%s: invalid header", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER); + break; + case ALL_DATA_READ: + event_del(&evcon->ev); + evrtsp_connection_done(evcon); + break; + case MORE_DATA_EXPECTED: + default: + evrtsp_add_event(&evcon->ev, evcon->timeout, + RTSP_READ_TIMEOUT); + break; + } +} + +static void +evrtsp_read_body(struct evrtsp_connection *evcon, struct evrtsp_request *req) +{ + struct evbuffer *buf = evcon->input_buffer; + + if (req->ntoread < 0) { + /* Read until connection close. */ + evbuffer_add_buffer(req->input_buffer, buf); + } else if (evbuffer_get_length(buf) >= req->ntoread) { + /* Completed content length */ + evbuffer_add(req->input_buffer, evbuffer_pullup(buf,-1), + (size_t)req->ntoread); + evbuffer_drain(buf, (size_t)req->ntoread); + req->ntoread = 0; + evrtsp_connection_done(evcon); + return; + } + /* Read more! */ + event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon); + evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT); +} + +/* + * Reads data into a buffer structure until no more data + * can be read on the file descriptor or we have read all + * the data that we wanted to read. + * Execute callback when done. + */ + +void +evrtsp_read(int fd, short what, void *arg) +{ + struct evrtsp_connection *evcon = arg; + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + struct evbuffer *buf = evcon->input_buffer; + int n; + + if (what == EV_TIMEOUT) { + event_warn("%s: read timeout", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT); + return; + } + n = evbuffer_read(buf, fd, -1); + event_debug(("%s: got %d on %d", __func__, n, fd)); + + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) { + event_warn("%s: evbuffer_read", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); + } else { + evrtsp_add_event(&evcon->ev, evcon->timeout, + RTSP_READ_TIMEOUT); + } + return; + } else if (n == 0) { + /* Connection closed */ + evcon->state = EVCON_DISCONNECTED; + evrtsp_connection_done(evcon); + return; + } + + if (evcon->ciphercb) + evcon->ciphercb(evcon->input_buffer, evcon->ciphercb_arg, 0); + + switch (evcon->state) { + case EVCON_READING_FIRSTLINE: + evrtsp_read_firstline(evcon, req); + break; + case EVCON_READING_HEADERS: + evrtsp_read_header(evcon, req); + break; + case EVCON_READING_BODY: + evrtsp_read_body(evcon, req); + break; + case EVCON_READING_TRAILER: + evrtsp_read_trailer(evcon, req); + break; + case EVCON_DISCONNECTED: + case EVCON_CONNECTING: + case EVCON_IDLE: + case EVCON_WRITING: + default: + event_errx(1, "%s: illegal connection state %d", + __func__, evcon->state); + } +} + +static void +evrtsp_write_connectioncb(struct evrtsp_connection *evcon, void *arg) +{ + /* This is after writing the request to the server */ + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + assert(req != NULL); + + assert(evcon->state == EVCON_WRITING); + + /* We are done writing our header and are now expecting the response */ + req->kind = EVRTSP_RESPONSE; + + evrtsp_start_read(evcon); +} + + + +/* + * Clean up a connection object + */ + +void +evrtsp_connection_free(struct evrtsp_connection *evcon) +{ + struct evrtsp_request *req; + + /* notify interested parties that this connection is going down */ + if (evcon->fd != -1) { + if (evrtsp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + } + + /* remove all requests that might be queued on this connection */ + while ((req = TAILQ_FIRST(&evcon->requests)) != NULL) { + TAILQ_REMOVE(&evcon->requests, req, next); + evrtsp_request_free(req); + } + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) + EVUTIL_CLOSESOCKET(evcon->fd); + + if (evcon->bind_address != NULL) + free(evcon->bind_address); + + if (evcon->address != NULL) + free(evcon->address); + + if (evcon->input_buffer != NULL) + evbuffer_free(evcon->input_buffer); + + if (evcon->output_buffer != NULL) + evbuffer_free(evcon->output_buffer); + + free(evcon); +} + +static void +evrtsp_request_dispatch(struct evrtsp_connection* evcon) +{ + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + + /* this should not usually happy but it's possible */ + if (req == NULL) + return; + + /* delete possible close detection events */ + evrtsp_connection_stop_detectclose(evcon); + + /* we assume that the connection is connected already */ + assert(evcon->state == EVCON_IDLE); + + evcon->state = EVCON_WRITING; + + /* Create the header from the store arguments */ + evrtsp_make_header(evcon, req); + + /* forked-daapd customisation for encryption */ + if (evcon->ciphercb) + evcon->ciphercb(evcon->output_buffer, evcon->ciphercb_arg, 1); + + evrtsp_write_buffer(evcon, evrtsp_write_connectioncb, NULL); +} + +/* Reset our connection state */ +void +evrtsp_connection_reset(struct evrtsp_connection *evcon) +{ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + + if (evcon->fd != -1) { + /* inform interested parties about connection close */ + if (evrtsp_connected(evcon) && evcon->closecb != NULL) + (*evcon->closecb)(evcon, evcon->closecb_arg); + + EVUTIL_CLOSESOCKET(evcon->fd); + evcon->fd = -1; + } + evcon->state = EVCON_DISCONNECTED; + + evbuffer_drain(evcon->input_buffer, + evbuffer_get_length(evcon->input_buffer)); + evbuffer_drain(evcon->output_buffer, + evbuffer_get_length(evcon->output_buffer)); +} + +static void +evrtsp_detect_close_cb(int fd, short what, void *arg) +{ + struct evrtsp_connection *evcon = arg; + + evrtsp_connection_reset(evcon); +} + +static void +evrtsp_connection_start_detectclose(struct evrtsp_connection *evcon) +{ + evcon->flags |= EVRTSP_CON_CLOSEDETECT; + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); + event_assign(&evcon->close_ev, evcon->base, evcon->fd, EV_READ, + evrtsp_detect_close_cb, evcon); + event_add(&evcon->close_ev, NULL); +} + +static void +evrtsp_connection_stop_detectclose(struct evrtsp_connection *evcon) +{ + evcon->flags &= ~EVRTSP_CON_CLOSEDETECT; + + if (event_initialized(&evcon->close_ev)) + event_del(&evcon->close_ev); +} + +/* + * Call back for asynchronous connection attempt. + */ + +static void +evrtsp_connectioncb(int fd, short what, void *arg) +{ + struct evrtsp_connection *evcon = arg; + int error; + socklen_t errsz = sizeof(error); + + if (what == EV_TIMEOUT) { + event_warnx("%s: connection timeout for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd); + goto cleanup; + } + + /* Check if the connection completed */ + if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, (void*)&error, + &errsz) == -1) { + event_warnx("%s: getsockopt for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd); + goto cleanup; + } + + if (error) { + event_warnx("%s: connect failed for \"%s:%d\" on %d: %s", + __func__, evcon->address, evcon->port, evcon->fd, + strerror(error)); + goto cleanup; + } + + /* We are connected to the server now */ + event_debug(("%s: connected to \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd)); + + evcon->state = EVCON_IDLE; + + /* try to start requests that have queued up on this connection */ + evrtsp_request_dispatch(evcon); + return; + + cleanup: + evrtsp_connection_reset(evcon); + + /* for now, we just signal all requests by executing their callbacks */ + while (TAILQ_FIRST(&evcon->requests) != NULL) { + struct evrtsp_request *request = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, request, next); + request->evcon = NULL; + + /* we might want to set an error here */ + request->cb(request, request->cb_arg); + evrtsp_request_free(request); + } +} + +/* + * Check if we got a valid response code. + */ + +static int +evrtsp_valid_response_code(int code) +{ + if (code == 0) + return (0); + + return (1); +} + +/* Parses the status line of an RTSP server */ + +static int +evrtsp_parse_response_line(struct evrtsp_request *req, char *line) +{ + char *protocol; + char *number; + const char *readable = ""; + + protocol = strsep(&line, " "); + if (line == NULL) + return (-1); + number = strsep(&line, " "); + if (line != NULL) + readable = line; + + if (strcmp(protocol, "RTSP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(protocol, "RTSP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_warnx("%s: bad protocol \"%s\"", + __func__, protocol); + return (-1); + } + + req->response_code = atoi(number); + if (!evrtsp_valid_response_code(req->response_code)) { + event_warnx("%s: bad response code \"%s\"", + __func__, number); + return (-1); + } + + if ((req->response_code_line = strdup(readable)) == NULL) + event_err(1, "%s: strdup", __func__); + + return (0); +} + +const char * +evrtsp_find_header(const struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + return (header->value); + } + + return (NULL); +} + +void +evrtsp_clear_headers(struct evkeyvalq *headers) +{ + struct evkeyval *header; + + for (header = TAILQ_FIRST(headers); + header != NULL; + header = TAILQ_FIRST(headers)) { + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + } +} + +/* + * Returns 0, if the header was successfully removed. + * Returns -1, if the header could not be found. + */ + +int +evrtsp_remove_header(struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + break; + } + + if (header == NULL) + return (-1); + + /* Free and remove the header that we found */ + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + + return (0); +} + +static int +evrtsp_header_is_valid_value(const char *value) +{ + const char *p = value; + + while ((p = strpbrk(p, "\r\n")) != NULL) { + /* we really expect only one new line */ + p += strspn(p, "\r\n"); + /* we expect a space or tab for continuation */ + if (*p != ' ' && *p != '\t') + return (0); + } + return (1); +} + +int +evrtsp_add_header(struct evkeyvalq *headers, + const char *key, const char *value) +{ + event_debug(("%s: key: %s val: %s", __func__, key, value)); + + if (strchr(key, '\r') != NULL || strchr(key, '\n') != NULL) { + /* drop illegal headers */ + event_warn("%s: dropping illegal header key", __func__); + return (-1); + } + + if (!evrtsp_header_is_valid_value(value)) { + event_warn("%s: dropping illegal header value", __func__); + return (-1); + } + + return (evrtsp_add_header_internal(headers, key, value)); +} + +static int +evrtsp_add_header_internal(struct evkeyvalq *headers, + const char *key, const char *value) +{ + struct evkeyval *header = calloc(1, sizeof(struct evkeyval)); + + if (header == NULL) { + event_warn("%s: calloc", __func__); + return (-1); + } + if ((header->key = strdup(key)) == NULL) { + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + if ((header->value = strdup(value)) == NULL) { + free(header->key); + free(header); + event_warn("%s: strdup", __func__); + return (-1); + } + + TAILQ_INSERT_TAIL(headers, header, next); + + return (0); +} + +/* + * Parses header lines from a request or a response into the specified + * request object given an event buffer. + * + * Returns + * DATA_CORRUPTED on error + * MORE_DATA_EXPECTED when we need to read more headers + * ALL_DATA_READ when all headers have been read. + */ + +enum message_read_status +evrtsp_parse_firstline(struct evrtsp_request *req, struct evbuffer *buffer) +{ + char *line; + enum message_read_status status = ALL_DATA_READ; + + line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY); + if (line == NULL) + return (MORE_DATA_EXPECTED); + + switch (req->kind) { + case EVRTSP_RESPONSE: + if (evrtsp_parse_response_line(req, line) == -1) + status = DATA_CORRUPTED; + break; + default: + status = DATA_CORRUPTED; + } + + free(line); + return (status); +} + +static int +evrtsp_append_to_last_header(struct evkeyvalq *headers, const char *line) +{ + struct evkeyval *header = TAILQ_LAST(headers, evkeyvalq); + char *newval; + size_t old_len, line_len; + + if (header == NULL) + return (-1); + + old_len = strlen(header->value); + line_len = strlen(line); + + newval = realloc(header->value, old_len + line_len + 1); + if (newval == NULL) + return (-1); + + memcpy(newval + old_len, line, line_len + 1); + header->value = newval; + + return (0); +} + +enum message_read_status +evrtsp_parse_headers(struct evrtsp_request *req, struct evbuffer *buffer) +{ + char *line; + enum message_read_status status = MORE_DATA_EXPECTED; + + struct evkeyvalq *headers = req->input_headers; + while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) + != NULL) { + char *skey, *svalue; + + if (*line == '\0') { /* Last header - Done */ + status = ALL_DATA_READ; + free(line); + break; + } + + /* Check if this is a continuation line */ + if (*line == ' ' || *line == '\t') { + if (evrtsp_append_to_last_header(headers, line) == -1) + goto error; + free(line); + continue; + } + + /* Processing of header lines */ + svalue = line; + skey = strsep(&svalue, ":"); + if (svalue == NULL) + goto error; + + svalue += strspn(svalue, " "); + + if (evrtsp_add_header(headers, skey, svalue) == -1) + goto error; + + free(line); + } + + return (status); + + error: + free(line); + return (DATA_CORRUPTED); +} + +static int +evrtsp_get_body_length(struct evrtsp_request *req) +{ + struct evkeyvalq *headers = req->input_headers; + const char *content_length; + + content_length = evrtsp_find_header(headers, "Content-Length"); + + if (content_length == NULL) { + /* If there is no Content-Length: header, a value of 0 is assumed, per spec. */ + req->ntoread = 0; + } else { + ev_int64_t ntoread = evutil_strtoll(content_length, NULL, 10); + if (*content_length == '\0' || ntoread < 0) { + event_warnx("%s: illegal content length: %s", + __func__, content_length); + return (-1); + } + req->ntoread = ntoread; + } + + event_debug(("%s: bytes to read: %lld (in buffer %zu)", + __func__, req->ntoread, + evbuffer_get_length(req->evcon->input_buffer))); + + return (0); +} + +static void +evrtsp_get_body(struct evrtsp_connection *evcon, struct evrtsp_request *req) +{ + evcon->state = EVCON_READING_BODY; + if (evrtsp_get_body_length(req) == -1) { + event_warn("%s: invalid body", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER); + return; + } + + evrtsp_read_body(evcon, req); +} + +static void +evrtsp_read_firstline(struct evrtsp_connection *evcon, + struct evrtsp_request *req) +{ + enum message_read_status res; + + res = evrtsp_parse_firstline(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_warnx("%s: bad header lines on %d", + __func__, evcon->fd); + evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evrtsp_add_event(&evcon->ev, + evcon->timeout, RTSP_READ_TIMEOUT); + return; + } + + evcon->state = EVCON_READING_HEADERS; + evrtsp_read_header(evcon, req); +} + +static void +evrtsp_read_header(struct evrtsp_connection *evcon, struct evrtsp_request *req) +{ + enum message_read_status res; + int fd = evcon->fd; + + res = evrtsp_parse_headers(req, evcon->input_buffer); + if (res == DATA_CORRUPTED) { + /* Error while reading, terminate */ + event_warnx("%s: bad header lines on %d", __func__, fd); + evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER); + return; + } else if (res == MORE_DATA_EXPECTED) { + /* Need more header lines */ + evrtsp_add_event(&evcon->ev, + evcon->timeout, RTSP_READ_TIMEOUT); + return; + } + + /* Done reading headers, do the real work */ + switch (req->kind) { + case EVRTSP_RESPONSE: + event_debug(("%s: start of read body on %d", + __func__, fd)); + evrtsp_get_body(evcon, req); + break; + + default: + event_warnx("%s: bad header on %d", __func__, fd); + evrtsp_connection_fail(evcon, EVCON_RTSP_INVALID_HEADER); + break; + } +} + +/* + * Creates a TCP connection to the specified port and executes a callback + * when finished. Failure or sucess is indicate by the passed connection + * object. + * + * Although this interface accepts a hostname, it is intended to take + * only numeric hostnames so that non-blocking DNS resolution can + * happen elsewhere. + */ + +struct evrtsp_connection * +evrtsp_connection_new(const char *address, unsigned short port) +{ + struct evrtsp_connection *evcon = NULL; + char *intf; + char *addr; + unsigned char scratch[16]; + int family; + + if ((addr = strdup(address)) == NULL) { + event_warn("%s: strdup failed", __func__); + goto error; + } + + intf = strchr(addr, '%'); + if (intf) + *intf = '\0'; + + if (inet_pton(AF_INET6, addr, scratch) == 1) + family = AF_INET6; + else if (inet_pton(AF_INET, addr, scratch) == 1) + family = AF_INET; + else { + free(addr); + event_warn("%s: address is neither IPv6 nor IPv4", __func__); + return NULL; + } + + if (intf) + *intf = '%'; + + event_debug(("Attempting connection to %s:%d", address, port)); + + if ((evcon = calloc(1, sizeof(struct evrtsp_connection))) == NULL) { + free(addr); + event_warn("%s: calloc failed", __func__); + goto error; + } + + evcon->fd = -1; + evcon->port = port; + + evcon->timeout = -1; + + evcon->cseq = 1; + + evcon->family = family; + evcon->address = addr; + + if ((evcon->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + if ((evcon->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + evcon->state = EVCON_DISCONNECTED; + TAILQ_INIT(&evcon->requests); + + return (evcon); + + error: + if (evcon != NULL) + evrtsp_connection_free(evcon); + return (NULL); +} + +void evrtsp_connection_set_base(struct evrtsp_connection *evcon, + struct event_base *base) +{ + assert(evcon->base == NULL); + assert(evcon->state == EVCON_DISCONNECTED); + evcon->base = base; +} + +void +evrtsp_connection_set_timeout(struct evrtsp_connection *evcon, + int timeout_in_secs) +{ + evcon->timeout = timeout_in_secs; +} + +void +evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, + void (*cb)(struct evrtsp_connection *, void *), void *cbarg) +{ + evcon->closecb = cb; + evcon->closecb_arg = cbarg; +} + +void +evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, + void (*cb)(struct evbuffer *, void *, int encrypt), void *cbarg) +{ + evcon->ciphercb = cb; + evcon->ciphercb_arg = cbarg; +} + +void +evrtsp_connection_get_local_address(struct evrtsp_connection *evcon, + char **address, u_short *port, int *family) +{ + union { + struct sockaddr_storage ss; + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + socklen_t slen; + int ret; + + *address = NULL; + *port = 0; + memset(&addr, 0, sizeof(addr)); + + if (!evrtsp_connected(evcon)) + return; + + slen = sizeof(struct sockaddr_storage); + ret = getsockname(evcon->fd, &addr.sa, &slen); + if (ret < 0) + return; + + name_from_addr(&addr.sa, slen, address, NULL); + + if (!*address) + return; + + *family = addr.sa.sa_family; + + switch (*family) + { + case AF_INET: + *port = ntohs(addr.sin.sin_port); + break; + +#ifdef AF_INET6 + case AF_INET6: + *port = ntohs(addr.sin6.sin6_port); + break; +#endif + + default: + free(*address); + *address = NULL; + + event_err(1, "%s: unhandled address family\n", __func__); + return; + } +} + +void +evrtsp_connection_get_peer(struct evrtsp_connection *evcon, + char **address, u_short *port) +{ + *address = evcon->address; + *port = evcon->port; +} + +int +evrtsp_connection_connect(struct evrtsp_connection *evcon) +{ + if (evcon->state == EVCON_CONNECTING) + return (0); + + evrtsp_connection_reset(evcon); + + evcon->fd = bind_socket(evcon->family, + evcon->bind_address, evcon->bind_port, 0 /*reuse*/); + if (evcon->fd == -1) { + event_warnx("%s: failed to bind to \"%s\"", + __func__, evcon->bind_address); + return (-1); + } + + if (socket_connect(evcon->fd, evcon->address, evcon->port) == -1) { + EVUTIL_CLOSESOCKET(evcon->fd); evcon->fd = -1; + return (-1); + } + + /* Set up a callback for successful connection setup */ + event_assign(&evcon->ev, evcon->base, evcon->fd, EV_WRITE, evrtsp_connectioncb, evcon); + evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_CONNECT_TIMEOUT); + + evcon->state = EVCON_CONNECTING; + + return (0); +} + +/* + * Starts an RTSP request on the provided evrtsp_connection object. + * If the connection object is not connected to the server already, + * this will start the connection. + */ + +int +evrtsp_make_request(struct evrtsp_connection *evcon, + struct evrtsp_request *req, + enum evrtsp_cmd_type type, const char *uri) +{ +event_debug(("%s: TEST", __func__)); + + /* We are making a request */ + req->kind = EVRTSP_REQUEST; + req->type = type; + if (req->uri != NULL) + free(req->uri); + if ((req->uri = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + /* Set the protocol version if it is not supplied */ + if (!req->major && !req->minor) { + req->major = 1; + req->minor = 0; + } + + assert(req->evcon == NULL); + req->evcon = evcon; + assert(!(req->flags & EVRTSP_REQ_OWN_CONNECTION)); + + TAILQ_INSERT_TAIL(&evcon->requests, req, next); + + /* If the connection object is not connected; make it so */ + if (!evrtsp_connected(evcon)) + return (evrtsp_connection_connect(evcon)); + + /* + * If it's connected already and we are the first in the queue, + * then we can dispatch this request immediately. Otherwise, it + * will be dispatched once the pending requests are completed. + */ + if (TAILQ_FIRST(&evcon->requests) == req) + evrtsp_request_dispatch(evcon); + + return (0); +} + +/* + * Reads data from file descriptor into request structure + * Request structure needs to be set up correctly. + */ + +void +evrtsp_start_read(struct evrtsp_connection *evcon) +{ + /* Set up an event to read the headers */ + if (event_initialized(&evcon->ev)) + event_del(&evcon->ev); + event_assign(&evcon->ev, evcon->base, evcon->fd, EV_READ, evrtsp_read, evcon); + evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT); + evcon->state = EVCON_READING_FIRSTLINE; +} + +static void +evrtsp_send_done(struct evrtsp_connection *evcon, void *arg) +{ + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + TAILQ_REMOVE(&evcon->requests, req, next); + + /* delete possible close detection events */ + evrtsp_connection_stop_detectclose(evcon); + + assert(req->flags & EVRTSP_REQ_OWN_CONNECTION); + evrtsp_request_free(req); +} + +/* Requires that headers and response code are already set up */ + +static inline void +evrtsp_send(struct evrtsp_request *req, struct evbuffer *databuf) +{ + struct evrtsp_connection *evcon = req->evcon; + + if (evcon == NULL) { + evrtsp_request_free(req); + return; + } + + assert(TAILQ_FIRST(&evcon->requests) == req); + + /* xxx: not sure if we really should expose the data buffer this way */ + if (databuf != NULL) + evbuffer_add_buffer(req->output_buffer, databuf); + + /* Adds headers to the response */ + evrtsp_make_header(evcon, req); + + evrtsp_write_buffer(evcon, evrtsp_send_done, NULL); +} + +/* + * Request related functions + */ + +struct evrtsp_request * +evrtsp_request_new(void (*cb)(struct evrtsp_request *, void *), void *arg) +{ + struct evrtsp_request *req = NULL; + + /* Allocate request structure */ + if ((req = calloc(1, sizeof(struct evrtsp_request))) == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + + req->kind = EVRTSP_RESPONSE; + req->input_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->input_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->input_headers); + + req->output_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->output_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->output_headers); + + if ((req->input_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + if ((req->output_buffer = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new", __func__); + goto error; + } + + req->cb = cb; + req->cb_arg = arg; + + return (req); + + error: + if (req != NULL) + evrtsp_request_free(req); + return (NULL); +} + +void +evrtsp_request_free(struct evrtsp_request *req) +{ + if (req->uri != NULL) + free(req->uri); + if (req->response_code_line != NULL) + free(req->response_code_line); + + evrtsp_clear_headers(req->input_headers); + free(req->input_headers); + + evrtsp_clear_headers(req->output_headers); + free(req->output_headers); + + if (req->input_buffer != NULL) + evbuffer_free(req->input_buffer); + + if (req->output_buffer != NULL) + evbuffer_free(req->output_buffer); + + free(req); +} + +/* + * Allows for inspection of the request URI + */ + +const char * +evrtsp_request_uri(struct evrtsp_request *req) { + if (req->uri == NULL) + event_warn("%s: request has no uri", __func__); + return (req->uri); +} + +/* + * Network helper functions that we do not want to export to the rest of + * the world. + */ +#if 0 /* Unused */ +static struct addrinfo * +addr_from_name(char *address) +{ +#ifdef EVENT__HAVE_GETADDRINFO + struct addrinfo ai, *aitop; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_RAW; + ai.ai_flags = 0; + if ((ai_result = getaddrinfo(address, NULL, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + } + + return (aitop); +#else + assert(0); + return NULL; /* XXXXX Use gethostbyname, if this function is ever used. */ +#endif +} +#endif + +static void +name_from_addr(struct sockaddr *sa, socklen_t salen, + char **phost, char **pport) +{ + char ntop[NI_MAXHOST]; + char strport[NI_MAXSERV]; + int ni_result; + +#ifdef EVENT__HAVE_GETNAMEINFO + ni_result = getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + + if (ni_result != 0) { + if (ni_result == EAI_SYSTEM) + event_err(1, "getnameinfo failed"); + else + event_errx(1, "getnameinfo failed: %s", gai_strerror(ni_result)); + return; + } +#else + ni_result = fake_getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ni_result != 0) + return; +#endif + if (phost) + *phost = strdup(ntop); + if (pport) + *pport = strdup(strport); +} + +/* Create a non-blocking socket and bind it */ +/* todo: rename this function */ +static int +bind_socket_ai(int family, struct addrinfo *ai, int reuse) +{ + int fd, on = 1, r; + int serrno; + + if (ai) + family = ai->ai_family; + + /* Create listen socket */ + fd = socket(family, SOCK_STREAM, 0); + if (fd == -1) { + event_warn("socket"); + return (-1); + } + + if (evutil_make_socket_nonblocking(fd) < 0) + goto out; + +#ifndef WIN32 + if (fcntl(fd, F_SETFD, 1) == -1) { + event_warn("fcntl(F_SETFD)"); + goto out; + } +#endif + + if (family == AF_INET6) + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + if (reuse) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&on, sizeof(on)); + } + + if (ai != NULL) { + r = bind(fd, ai->ai_addr, ai->ai_addrlen); + if (r == -1) + goto out; + } + + return (fd); + + out: + serrno = EVUTIL_SOCKET_ERROR(); + EVUTIL_CLOSESOCKET(fd); + EVUTIL_SET_SOCKET_ERROR(serrno); + return (-1); +} + +static struct addrinfo * +make_addrinfo(const char *address, u_short port) +{ + struct addrinfo *aitop = NULL; + +#ifdef EVENT__HAVE_GETADDRINFO + struct addrinfo ai; + char strport[NI_MAXSERV]; + int ai_result; + + memset(&ai, 0, sizeof(ai)); + ai.ai_family = AF_UNSPEC; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = AI_PASSIVE; /* turn NULL host name into INADDR_ANY */ + evutil_snprintf(strport, sizeof(strport), "%d", port); + if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) { + if ( ai_result == EAI_SYSTEM ) + event_warn("getaddrinfo"); + else + event_warnx("getaddrinfo: %s", gai_strerror(ai_result)); + return (NULL); + } +#else + static int cur; + static struct addrinfo ai[2]; /* We will be returning the address of some of this memory so it has to last even after this call. */ + if (++cur == 2) cur = 0; /* allow calling this function twice */ + + if (fake_getaddrinfo(address, &ai[cur]) < 0) { + event_warn("fake_getaddrinfo"); + return (NULL); + } + aitop = &ai[cur]; + ((struct sockaddr_in *) aitop->ai_addr)->sin_port = htons(port); +#endif + + return (aitop); +} + +static int +bind_socket(int family, const char *address, u_short port, int reuse) +{ + int fd; + struct addrinfo *aitop = NULL; + + /* just create an unbound socket */ + if (address == NULL && port == 0) + return bind_socket_ai(family, NULL, 0); + + aitop = make_addrinfo(address, port); + + if (aitop == NULL) + return (-1); + + fd = bind_socket_ai(family, aitop, reuse); + +#ifdef EVENT__HAVE_GETADDRINFO + freeaddrinfo(aitop); +#else + fake_freeaddrinfo(aitop); +#endif + + return (fd); +} + +static int +socket_connect(int fd, const char *address, unsigned short port) +{ + struct addrinfo *ai = make_addrinfo(address, port); + int res = -1; + + if (ai == NULL) { + event_warnx("%s: make_addrinfo: \"%s:%d\"", + __func__, address, port); + return (-1); + } + + if (connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { +#ifdef WIN32 + int tmp_error = WSAGetLastError(); + if (tmp_error != WSAEWOULDBLOCK && tmp_error != WSAEINVAL && + tmp_error != WSAEINPROGRESS) { + goto out; + } +#else + if (errno != EINPROGRESS) { + goto out; + } +#endif + } + + /* everything is fine */ + res = 0; + +out: +#ifdef EVENT__HAVE_GETADDRINFO + freeaddrinfo(ai); +#else + fake_freeaddrinfo(ai); +#endif + + return (res); +} diff --git a/pair_ap/pair_fruit.c b/pair_ap/pair_fruit.c new file mode 100644 index 00000000..42e0b066 --- /dev/null +++ b/pair_ap/pair_fruit.c @@ -0,0 +1,1063 @@ +/* + * + * The Secure Remote Password 6a implementation is adapted from: + * - Tom Cocagne + * + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include + +#include +#include + +#include + +#include "pair-internal.h" + +/* ----------------------------- DEFINES ETC ------------------------------- */ + +#define USERNAME "12:34:56:78:90:AB" +#define EPK_LENGTH 32 +#define AUTHTAG_LENGTH 16 +#define AES_SETUP_KEY "Pair-Setup-AES-Key" +#define AES_SETUP_IV "Pair-Setup-AES-IV" +#define AES_VERIFY_KEY "Pair-Verify-AES-Key" +#define AES_VERIFY_IV "Pair-Verify-AES-IV" + + +/* ---------------------------------- SRP ---------------------------------- */ + +typedef enum +{ + SRP_NG_2048, + SRP_NG_CUSTOM +} SRP_NGType; + +typedef struct +{ + bnum N; + bnum g; +} NGConstant; + +struct SRPUser +{ + enum hash_alg alg; + NGConstant *ng; + + bnum a; + bnum A; + bnum S; + + const unsigned char *bytes_A; + int authenticated; + + char *username; + unsigned char *password; + int password_len; + + unsigned char M [SHA512_DIGEST_LENGTH]; + unsigned char H_AMK [SHA512_DIGEST_LENGTH]; + unsigned char session_key [2 * SHA512_DIGEST_LENGTH]; // See hash_session_key() + int session_key_len; +}; + +struct NGHex +{ + const char *n_hex; + const char *g_hex; +}; + +// We only need 2048 right now, but keep the array in case we want to add others later +// All constants here were pulled from Appendix A of RFC 5054 +static struct NGHex global_Ng_constants[] = +{ + { /* 2048 */ + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + {0,0} /* null sentinel */ +}; + + +static NGConstant * +new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) +{ + NGConstant *ng = calloc(1, sizeof(NGConstant)); + + if ( ng_type != SRP_NG_CUSTOM ) + { + n_hex = global_Ng_constants[ ng_type ].n_hex; + g_hex = global_Ng_constants[ ng_type ].g_hex; + } + + bnum_hex2bn(ng->N, n_hex); + bnum_hex2bn(ng->g, g_hex); + + return ng; +} + +static void +free_ng(NGConstant * ng) +{ + if (!ng) + return; + + bnum_free(ng->N); + bnum_free(ng->g); + free(ng); +} + +static bnum +calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) +{ + unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + + hash_init( alg, &ctx ); + hash_update( alg, &ctx, username, strlen(username) ); + hash_update( alg, &ctx, ":", 1 ); + hash_update( alg, &ctx, password, password_len ); + hash_final( alg, &ctx, ucp_hash ); + + return H_ns( alg, salt, ucp_hash, hash_length(alg) ); +} + +static int +hash_session_key(enum hash_alg alg, const bnum n, unsigned char *dest) +{ + int nbytes = bnum_num_bytes(n); + unsigned char *bin = malloc(nbytes); + unsigned char fourbytes[4] = { 0 }; // Only God knows the reason for this, and perhaps some poor soul at Apple + + bnum_bn2bin(n, bin, nbytes); + + hash_ab(alg, dest, bin, nbytes, fourbytes, sizeof(fourbytes)); + + fourbytes[3] = 1; // Again, only ... + + hash_ab(alg, dest + hash_length(alg), bin, nbytes, fourbytes, sizeof(fourbytes)); + + free(bin); + + return (2 * hash_length(alg)); +} + +static void +calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s, + const bnum A, const bnum B, const unsigned char *K, int K_len) +{ + unsigned char H_N[ SHA512_DIGEST_LENGTH ]; + unsigned char H_g[ SHA512_DIGEST_LENGTH ]; + unsigned char H_I[ SHA512_DIGEST_LENGTH ]; + unsigned char H_xor[ SHA512_DIGEST_LENGTH ]; + HashCTX ctx; + int i = 0; + int hash_len = hash_length(alg); + + hash_num( alg, ng->N, H_N ); + hash_num( alg, ng->g, H_g ); + + hash(alg, (const unsigned char *)I, strlen(I), H_I); + + for (i=0; i < hash_len; i++ ) + H_xor[i] = H_N[i] ^ H_g[i]; + + hash_init( alg, &ctx ); + + hash_update( alg, &ctx, H_xor, hash_len ); + hash_update( alg, &ctx, H_I, hash_len ); + update_hash_n( alg, &ctx, s ); + update_hash_n( alg, &ctx, A ); + update_hash_n( alg, &ctx, B ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static void +calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) +{ + HashCTX ctx; + + hash_init( alg, &ctx ); + + update_hash_n( alg, &ctx, A ); + hash_update( alg, &ctx, M, hash_length(alg) ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static struct SRPUser * +srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, + const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = calloc(1, sizeof(struct SRPUser)); + int ulen = strlen(username) + 1; + + if (!usr) + goto err_exit; + + usr->alg = alg; + usr->ng = new_ng( ng_type, n_hex, g_hex ); + + bnum_new(usr->a); + bnum_new(usr->A); + bnum_new(usr->S); + + if (!usr->ng || !usr->a || !usr->A || !usr->S) + goto err_exit; + + usr->username = malloc(ulen); + usr->password = malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy(usr->username, username, ulen); + memcpy(usr->password, bytes_password, len_password); + + usr->authenticated = 0; + usr->bytes_A = 0; + + return usr; + + err_exit: + if (!usr) + return NULL; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free(usr->username); + if (usr->password) + { + memset(usr->password, 0, usr->password_len); + free(usr->password); + } + free(usr); + + return NULL; +} + +static void +srp_user_free(struct SRPUser *usr) +{ + if(!usr) + return; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free_ng(usr->ng); + + memset(usr->password, 0, usr->password_len); + + free(usr->username); + free(usr->password); + free((char *)usr->bytes_A); + + memset(usr, 0, sizeof(*usr)); + free(usr); +} + +static int +srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + +static const unsigned char * +srp_user_get_session_key(struct SRPUser *usr, int *key_length) +{ + if (key_length) + *key_length = usr->session_key_len; + return usr->session_key; +} + +/* Output: username, bytes_A, len_A */ +static void +srp_user_start_authentication(struct SRPUser *usr, const char **username, + const unsigned char **bytes_A, int *len_A) +{ + bnum_random(usr->a, 256); + bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); + + *len_A = bnum_num_bytes(usr->A); + *bytes_A = malloc(*len_A); + + if (!*bytes_A) + { + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); + + usr->bytes_A = *bytes_A; + *username = usr->username; +} + +/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ +static void +srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, + const unsigned char **bytes_M, int *len_M ) +{ + bnum s, B, k, v; + bnum tmp1, tmp2, tmp3; + bnum u, x; + + *len_M = 0; + *bytes_M = 0; + + bnum_bin2bn(s, bytes_s, len_s); + bnum_bin2bn(B, bytes_B, len_B); + k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g); + bnum_new(v); + bnum_new(tmp1); + bnum_new(tmp2); + bnum_new(tmp3); + + if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) + goto cleanup1; + + u = H_nn_pad(usr->alg, usr->A, B); + x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len); + if (!u || !x) + goto cleanup2; + + // SRP-6a safety check + if (!bnum_is_zero(B) && !bnum_is_zero(u)) + { + bnum_modexp(v, usr->ng->g, x, usr->ng->N); + + // S = (B - k*(g^x)) ^ (a + ux) + bnum_mul(tmp1, u, x); + bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) + bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N); + bnum_mul(tmp3, k, tmp1); // tmp3 = k*(g^x) + bnum_sub(tmp1, B, tmp3); // tmp1 = (B - K*(g^x)) + bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); + + usr->session_key_len = hash_session_key(usr->alg, usr->S, usr->session_key); + + calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); + calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length(usr->alg); + } + else + { + *bytes_M = NULL; + if (len_M) + *len_M = 0; + } + + cleanup2: + bnum_free(x); + bnum_free(u); + cleanup1: + bnum_free(tmp3); + bnum_free(tmp2); + bnum_free(tmp1); + bnum_free(v); + bnum_free(k); + bnum_free(B); + bnum_free(s); +} + +static void +srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0) + usr->authenticated = 1; +} + + +/* -------------------------------- HELPERS -------------------------------- */ + +static int +encrypt_gcm(unsigned char *ciphertext, int ciphertext_len, unsigned char *tag, unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, const char **errmsg) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + *errmsg = NULL; + + if ( !(ctx = EVP_CIPHER_CTX_new()) || + (EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL) != 1) || + (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL) != 1) || + (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) ) + { + *errmsg = "Error initialising AES 128 GCM encryption"; + goto error; + } + + if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1) + { + *errmsg = "Error GCM encrypting"; + goto error; + } + + if (len > ciphertext_len) + { + *errmsg = "Bug! Buffer overflow"; + goto error; + } + + if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) + { + *errmsg = "Error finalising GCM encryption"; + goto error; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTHTAG_LENGTH, tag) != 1) + { + *errmsg = "Error getting authtag"; + goto error; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + gcry_error_t err; + + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0); + if (err) + { + *errmsg = "Error initialising AES 128 GCM encryption"; + return -1; + } + + err = gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)); + if (err) + { + *errmsg = "Could not set key for AES 128 GCM"; + goto error; + } + + err = gcry_cipher_setiv(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)); + if (err) + { + *errmsg = "Could not set iv for AES 128 GCM"; + goto error; + } + + err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext, plaintext_len); + if (err) + { + *errmsg = "Error GCM encrypting"; + goto error; + } + + err = gcry_cipher_gettag(hd, tag, AUTHTAG_LENGTH); + if (err) + { + *errmsg = "Error getting authtag"; + goto error; + } + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#endif +} + +static int +encrypt_ctr(unsigned char *ciphertext, int ciphertext_len, + unsigned char *plaintext1, int plaintext1_len, unsigned char *plaintext2, int plaintext2_len, + unsigned char *key, unsigned char *iv, const char **errmsg) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + *errmsg = NULL; + + if ( !(ctx = EVP_CIPHER_CTX_new()) || (EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv) != 1) ) + { + *errmsg = "Error initialising AES 128 CTR encryption"; + goto error; + } + + if ( (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext1, plaintext1_len) != 1) || + (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext2, plaintext2_len) != 1) ) + { + *errmsg = "Error CTR encrypting"; + goto error; + } + + if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) + { + *errmsg = "Error finalising encryption"; + goto error; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + gcry_error_t err; + + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CTR, 0); + if (err) + { + *errmsg = "Error initialising AES 128 CTR encryption"; + return -1; + } + err = gcry_cipher_setkey(hd, key, gcry_cipher_get_algo_keylen(GCRY_CIPHER_AES128)); + if (err) + { + *errmsg = "Could not set key for AES 128 CTR"; + goto error; + } + + err = gcry_cipher_setctr(hd, iv, gcry_cipher_get_algo_blklen(GCRY_CIPHER_AES128)); + if (err) + { + *errmsg = "Could not set iv for AES 128 CTR"; + goto error; + } + + err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext1, plaintext1_len); + if (err) + { + *errmsg = "Error CTR encrypting plaintext 1"; + goto error; + } + + err = gcry_cipher_encrypt(hd, ciphertext, ciphertext_len, plaintext2, plaintext2_len); + if (err) + { + *errmsg = "Error CTR encrypting plaintext 2"; + goto error; + } + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#endif +} + + +/* -------------------------- IMPLEMENTATION -------------------------------- */ + +static int +client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + + if (sodium_init() == -1) + return -1; + + if (!pin || strlen(pin) < 4) + return -1; + + memcpy(sctx->pin, pin, sizeof(sctx->pin)); + + return 0; +} + +static void +client_setup_free(struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + + srp_user_free(sctx->user); + + free(sctx->pkB); + free(sctx->M2); + free(sctx->salt); + free(sctx->epk); + free(sctx->authtag); +} + +static uint8_t * +client_setup_request1(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t method; + plist_t user; + uint32_t uint32; + char *data = NULL; // Necessary to initialize because plist_to_bin() uses value + + sctx->user = srp_user_new(HASH_SHA1, SRP_NG_2048, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0); + + dict = plist_new_dict(); + + method = plist_new_string("pin"); + user = plist_new_string(USERNAME); + + plist_dict_set_item(dict, "method", method); + plist_dict_set_item(dict, "user", user); + plist_to_bin(dict, &data, &uint32); + plist_free(dict); + + *len = (size_t)uint32; + return (uint8_t *)data; +} + +static uint8_t * +client_setup_request2(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t pk; + plist_t proof; + const char *auth_username = NULL; + uint32_t uint32; + char *data = NULL; + + // Calculate A + srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len); + + // Calculate M1 (client proof) + srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len); + + pk = plist_new_data((char *)sctx->pkA, sctx->pkA_len); + proof = plist_new_data((char *)sctx->M1, sctx->M1_len); + + dict = plist_new_dict(); + plist_dict_set_item(dict, "pk", pk); + plist_dict_set_item(dict, "proof", proof); + plist_to_bin(dict, &data, &uint32); + plist_free(dict); + + *len = (size_t)uint32; + return (uint8_t *)data; +} + +static uint8_t * +client_setup_request3(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t epk; + plist_t authtag; + uint32_t uint32; + char *data = NULL; + const unsigned char *session_key; + int session_key_len; + unsigned char key[SHA512_DIGEST_LENGTH]; + unsigned char iv[SHA512_DIGEST_LENGTH]; + unsigned char encrypted[128]; // Alloc a bit extra - should only need 2*16 + unsigned char tag[16]; + const char *errmsg; + int ret; + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + handle->errmsg = "Setup request 3: No valid session key"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_SETUP_KEY, strlen(AES_SETUP_KEY), session_key, session_key_len); + if (ret < 0) + { + handle->errmsg = "Setup request 3: Hashing of key string and shared secret failed"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_SETUP_IV, strlen(AES_SETUP_IV), session_key, session_key_len); + if (ret < 0) + { + handle->errmsg = "Setup request 3: Hashing of iv string and shared secret failed"; + return NULL; + } + + iv[15]++; // Nonce? +/* + if (iv[15] == 0x00 || iv[15] == 0xff) + printf("- note that value of last byte is %d!\n", iv[15]); +*/ + crypto_sign_keypair(sctx->public_key, sctx->private_key); + + ret = encrypt_gcm(encrypted, sizeof(encrypted), tag, sctx->public_key, sizeof(sctx->public_key), key, iv, &errmsg); + if (ret < 0) + { + handle->errmsg = errmsg; + return NULL; + } + + epk = plist_new_data((char *)encrypted, EPK_LENGTH); + authtag = plist_new_data((char *)tag, AUTHTAG_LENGTH); + + dict = plist_new_dict(); + plist_dict_set_item(dict, "epk", epk); + plist_dict_set_item(dict, "authTag", authtag); + plist_to_bin(dict, &data, &uint32); + plist_free(dict); + + *len = (size_t)uint32; + return (uint8_t *)data; +} + +static int +client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t pk; + plist_t salt; + + plist_from_bin((const char *)data, data_len, &dict); + + pk = plist_dict_get_item(dict, "pk"); + salt = plist_dict_get_item(dict, "salt"); + if (!pk || !salt) + { + handle->errmsg = "Setup response 1: Missing pk or salt"; + plist_free(dict); + return -1; + } + + plist_get_data_val(pk, (char **)&sctx->pkB, &sctx->pkB_len); // B + plist_get_data_val(salt, (char **)&sctx->salt, &sctx->salt_len); + + plist_free(dict); + + return 0; +} + +static int +client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t proof; + + plist_from_bin((const char *)data, data_len, &dict); + + proof = plist_dict_get_item(dict, "proof"); + if (!proof) + { + handle->errmsg = "Setup response 2: Missing proof"; + plist_free(dict); + return -1; + } + + plist_get_data_val(proof, (char **)&sctx->M2, &sctx->M2_len); // M2 + + plist_free(dict); + + // Check M2 + srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2); + if (!srp_user_is_authenticated(sctx->user)) + { + handle->errmsg = "Setup response 2: Server authentication failed"; + return -1; + } + + return 0; +} + +static int +client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + plist_t dict; + plist_t epk; + plist_t authtag; + + plist_from_bin((const char *)data, data_len, &dict); + + epk = plist_dict_get_item(dict, "epk"); + if (!epk) + { + handle->errmsg = "Setup response 3: Missing epk"; + plist_free(dict); + return -1; + } + + plist_get_data_val(epk, (char **)&sctx->epk, &sctx->epk_len); + + authtag = plist_dict_get_item(dict, "authTag"); + if (!authtag) + { + handle->errmsg = "Setup response 3: Missing authTag"; + plist_free(dict); + return -1; + } + + plist_get_data_val(authtag, (char **)&sctx->authtag, &sctx->authtag_len); + + plist_free(dict); + + assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key)); + assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key)); + + memcpy(handle->result.client_private_key, sctx->private_key, sizeof(sctx->private_key)); + memcpy(handle->result.client_public_key, sctx->public_key, sizeof(sctx->public_key)); + + handle->status = PAIR_STATUS_COMPLETED; + return 0; +} + +static int +client_setup_result(struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + char *ptr; + int i; + + // Last 32 bytes of the private key is the public key, so we don't need to + // explicitly export that + ptr = handle->result_str; + for (i = 0; i < sizeof(sctx->private_key); i++) + ptr += sprintf(ptr, "%02x", sctx->private_key[i]); // 2 x 64 bytes + *ptr = '\0'; + + return 0; +} + + +static int +client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + char hex[] = { 0, 0, 0 }; + size_t hexkey_len; + const char *ptr; + int i; + + if (sodium_init() == -1) + return -1; + + if (!client_setup_keys) + return -1; + + hexkey_len = strlen(client_setup_keys); + + if (hexkey_len != 2 * sizeof(vctx->client_private_key)) + return -1; + + if (device_id && strlen(device_id) != 16) + return -1; + + if (device_id) + memcpy(vctx->device_id, device_id, strlen(device_id)); + + ptr = client_setup_keys; + for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2) + { + hex[0] = ptr[0]; + hex[1] = ptr[1]; + vctx->client_private_key[i] = strtol(hex, NULL, 16); + } + + crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key); + + return 0; +} + +static uint8_t * +client_verify_request1(size_t *len, struct pair_verify_context *handle) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + const uint8_t basepoint[32] = {9}; + uint8_t *data; + int ret; + + ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint); + if (ret < 0) + { + handle->errmsg = "Verify request 1: Curve 25519 returned an error"; + return NULL; + } + + *len = 4 + sizeof(vctx->client_eph_public_key) + sizeof(vctx->client_public_key); + data = calloc(1, *len); + if (!data) + { + handle->errmsg = "Verify request 1: Out of memory"; + return NULL; + } + + data[0] = 1; // Magic + memcpy(data + 4, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + memcpy(data + 4 + sizeof(vctx->client_eph_public_key), vctx->client_public_key, sizeof(vctx->client_public_key)); + + return data; +} + +static uint8_t * +client_verify_request2(size_t *len, struct pair_verify_context *handle) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + uint8_t shared_secret[crypto_scalarmult_BYTES]; + uint8_t key[SHA512_DIGEST_LENGTH]; + uint8_t iv[SHA512_DIGEST_LENGTH]; + uint8_t encrypted[128]; // Alloc a bit extra, should only really need size of public key len + uint8_t signature[crypto_sign_BYTES]; + uint8_t *data; + int ret; + const char *errmsg; + + *len = sizeof(vctx->client_eph_public_key) + sizeof(vctx->server_eph_public_key); + data = calloc(1, *len); + if (!data) + { + handle->errmsg = "Verify request 2: Out of memory"; + return NULL; + } + + memcpy(data, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + memcpy(data + sizeof(vctx->client_eph_public_key), vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); + + crypto_sign_detached(signature, NULL, data, *len, vctx->client_private_key); + + free(data); + + ret = crypto_scalarmult(shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key); + if (ret < 0) + { + handle->errmsg = "Verify request 2: Curve 25519 returned an error"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, key, (unsigned char *)AES_VERIFY_KEY, strlen(AES_VERIFY_KEY), shared_secret, sizeof(shared_secret)); + if (ret < 0) + { + handle->errmsg = "Verify request 2: Hashing of key string and shared secret failed"; + return NULL; + } + + ret = hash_ab(HASH_SHA512, iv, (unsigned char *)AES_VERIFY_IV, strlen(AES_VERIFY_IV), shared_secret, sizeof(shared_secret)); + if (ret < 0) + { + handle->errmsg = "Verify request 2: Hashing of iv string and shared secret failed"; + return NULL; + } + + ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_fruit_public_key, sizeof(vctx->server_fruit_public_key), signature, sizeof(signature), key, iv, &errmsg); + if (ret < 0) + { + handle->errmsg = errmsg; + return NULL; + } + + *len = 4 + sizeof(vctx->server_fruit_public_key); + data = calloc(1, *len); + if (!data) + { + handle->errmsg = "Verify request 2: Out of memory"; + return NULL; + } + + memcpy(data + 4, encrypted, sizeof(vctx->server_fruit_public_key)); + + return data; +} + +static int +client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + size_t wanted; + + wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_fruit_public_key); + if (data_len < wanted) + { + handle->errmsg = "Verify response 2: Unexpected response (too short)"; + return -1; + } + + memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key)); + memcpy(vctx->server_fruit_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_fruit_public_key)); + + return 0; +} + +static int +client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + // TODO actually check response + + memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret)); + handle->result.shared_secret_len = sizeof(vctx->shared_secret); + + handle->status = PAIR_STATUS_COMPLETED; + + return 0; +} + + +struct pair_definition pair_client_fruit = +{ + .pair_setup_new = client_setup_new, + .pair_setup_free = client_setup_free, + .pair_setup_result = client_setup_result, + + .pair_setup_request1 = client_setup_request1, + .pair_setup_request2 = client_setup_request2, + .pair_setup_request3 = client_setup_request3, + + .pair_setup_response1 = client_setup_response1, + .pair_setup_response2 = client_setup_response2, + .pair_setup_response3 = client_setup_response3, + + .pair_verify_new = client_verify_new, + + .pair_verify_request1 = client_verify_request1, + .pair_verify_request2 = client_verify_request2, + + .pair_verify_response1 = client_verify_response1, + .pair_verify_response2 = client_verify_response2, +}; diff --git a/pair_ap/pair_homekit.c b/pair_ap/pair_homekit.c new file mode 100644 index 00000000..076ea14a --- /dev/null +++ b/pair_ap/pair_homekit.c @@ -0,0 +1,3123 @@ +/* + * Adaption of ap2-sender by ViktoriiaKh: + * + * + * To test, it is useful to try with this receiver: + * + * + * The Secure Remote Password 6a implementation is adapted from: + * - Tom Cocagne + * + * + * TLV helpers are adapted from ESP homekit: + * + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include +#include + +#include + +#include + +#include "pair-internal.h" +#include "pair-tlv.h" + + +/* ----------------------------- DEFINES ETC ------------------------------- */ + +#define USERNAME "Pair-Setup" +#define AUTHTAG_LENGTH 16 +#define NONCE_LENGTH 12 // 96 bits according to chacha poly1305 +#define REQUEST_BUFSIZE 4096 +#define ENCRYPTED_LEN_MAX 0x400 + +enum pair_keys +{ + PAIR_SETUP_MSG01 = 0, + PAIR_SETUP_MSG02, + PAIR_SETUP_MSG03, + PAIR_SETUP_MSG04, + PAIR_SETUP_MSG05, + PAIR_SETUP_MSG06, + PAIR_SETUP_CONTROLLER_SIGN, + PAIR_SETUP_ACCESSORY_SIGN, + PAIR_VERIFY_MSG01, + PAIR_VERIFY_MSG02, + PAIR_VERIFY_MSG03, + PAIR_VERIFY_MSG04, + PAIR_CONTROL_WRITE, + PAIR_CONTROL_READ, + PAIR_EVENTS_WRITE, + PAIR_EVENTS_READ, +}; + +struct pair_keys_map +{ + uint8_t state; + const char *salt; + const char *info; + const char nonce[8]; +}; + +static struct pair_keys_map pair_keys_map[] = +{ + // Used for /pair-setup + { 0x01, NULL, NULL, "" }, + { 0x02, NULL, NULL, "" }, + { 0x03, NULL, NULL, "" }, + { 0x04, NULL, NULL, "" }, + { 0x05, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg05" }, + { 0x06, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg06" }, + { 0, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", "" }, + { 0, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", "" }, + + // Used for /pair-verify + { 0x01, NULL, NULL, "" }, + { 0x02, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg02" }, + { 0x03, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg03" }, + { 0x04, NULL, NULL, "" }, + + // Encryption/decryption of control channel + { 0, "Control-Salt", "Control-Write-Encryption-Key", "" }, + { 0, "Control-Salt", "Control-Read-Encryption-Key", "" }, + + // Encryption/decryption of event channel + { 0, "Events-Salt", "Events-Write-Encryption-Key", "" }, + { 0, "Events-Salt", "Events-Read-Encryption-Key", "" }, +}; + +enum pair_method { + PairingMethodPairSetup = 0x00, + PairingMethodPairSetupWithAuth = 0x01, + PairingMethodPairVerify = 0x02, + PairingMethodAddPairing = 0x03, + PairingMethodRemovePairing = 0x04, + PairingMethodListPairings = 0x05 +}; + +enum pair_flags { + PairingFlagsTransient = 0x10, +}; + +// Forwards +const struct pair_definition pair_client_homekit_normal; +const struct pair_definition pair_client_homekit_transient; +const struct pair_definition pair_server_homekit; + + +/* ---------------------------------- SRP ----------------------------------- */ + +typedef enum +{ + SRP_NG_2048, + SRP_NG_3072, + SRP_NG_CUSTOM +} SRP_NGType; + +typedef struct +{ + bnum N; + bnum g; +} NGConstant; + +struct SRPUser +{ + enum hash_alg alg; + NGConstant *ng; + + bnum a; + bnum A; + bnum S; + + const unsigned char *bytes_A; + int authenticated; + + char *username; + unsigned char *password; + int password_len; + + unsigned char M [SHA512_DIGEST_LENGTH]; + unsigned char H_AMK [SHA512_DIGEST_LENGTH]; + unsigned char session_key [SHA512_DIGEST_LENGTH]; + int session_key_len; +}; + +struct SRPVerifier +{ + enum hash_alg alg; + NGConstant *ng; + + const unsigned char *bytes_B; + int authenticated; + + char *username; + + unsigned char M [SHA512_DIGEST_LENGTH]; + unsigned char H_AMK [SHA512_DIGEST_LENGTH]; + unsigned char session_key [SHA512_DIGEST_LENGTH]; + int session_key_len; +}; + +struct NGHex +{ + const char *n_hex; + const char *g_hex; +}; + +// These constants here were pulled from Appendix A of RFC 5054 +static struct NGHex global_Ng_constants[] = +{ + { /* 2048 */ + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + { /* 3072 */ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B" + "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485" + "B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F" + "E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23" + "DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32" + "905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558" + "17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521" + "ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7" + "1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817" + "7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82" + "D120A93AD2CAFFFFFFFFFFFFFFFF", + "5" + }, + {0,0} /* null sentinel */ +}; + + +static NGConstant * +new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) +{ + NGConstant *ng = calloc(1, sizeof(NGConstant)); + + if ( ng_type != SRP_NG_CUSTOM ) + { + n_hex = global_Ng_constants[ ng_type ].n_hex; + g_hex = global_Ng_constants[ ng_type ].g_hex; + } + + bnum_hex2bn(ng->N, n_hex); + bnum_hex2bn(ng->g, g_hex); + + return ng; +} + +static void +free_ng(NGConstant * ng) +{ + if (!ng) + return; + + bnum_free(ng->N); + bnum_free(ng->g); + free(ng); +} + +static bnum +calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) +{ + unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + + hash_init( alg, &ctx ); + hash_update( alg, &ctx, username, strlen(username) ); + hash_update( alg, &ctx, ":", 1 ); + hash_update( alg, &ctx, password, password_len ); + hash_final( alg, &ctx, ucp_hash ); + + return H_ns( alg, salt, ucp_hash, hash_length(alg) ); +} + +static void +calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s, + const bnum A, const bnum B, const unsigned char *K, int K_len) +{ + unsigned char H_N[ SHA512_DIGEST_LENGTH ]; + unsigned char H_g[ SHA512_DIGEST_LENGTH ]; + unsigned char H_I[ SHA512_DIGEST_LENGTH ]; + unsigned char H_xor[ SHA512_DIGEST_LENGTH ]; + HashCTX ctx; + int i = 0; + int hash_len = hash_length(alg); + + hash_num( alg, ng->N, H_N ); + hash_num( alg, ng->g, H_g ); + + hash(alg, (const unsigned char *)I, strlen(I), H_I); + + for (i=0; i < hash_len; i++ ) + H_xor[i] = H_N[i] ^ H_g[i]; + + hash_init( alg, &ctx ); + + hash_update( alg, &ctx, H_xor, hash_len ); + hash_update( alg, &ctx, H_I, hash_len ); + update_hash_n( alg, &ctx, s ); + update_hash_n( alg, &ctx, A ); + update_hash_n( alg, &ctx, B ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +static void +calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) +{ + HashCTX ctx; + + hash_init( alg, &ctx ); + + update_hash_n( alg, &ctx, A ); + hash_update( alg, &ctx, M, hash_length(alg) ); + hash_update( alg, &ctx, K, K_len ); + + hash_final( alg, &ctx, dest ); +} + +/* ----------------------- SRP for the client side -------------------------- */ + +static struct SRPUser * +srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, + const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = calloc(1, sizeof(struct SRPUser)); + int ulen = strlen(username) + 1; + + if (!usr) + goto err_exit; + + usr->alg = alg; + usr->ng = new_ng( ng_type, n_hex, g_hex ); + + bnum_new(usr->a); + bnum_new(usr->A); + bnum_new(usr->S); + + if (!usr->ng || !usr->a || !usr->A || !usr->S) + goto err_exit; + + usr->username = malloc(ulen); + usr->password = malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy(usr->username, username, ulen); + memcpy(usr->password, bytes_password, len_password); + + usr->authenticated = 0; + usr->bytes_A = 0; + + return usr; + + err_exit: + if (!usr) + return NULL; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free(usr->username); + if (usr->password) + { + memset(usr->password, 0, usr->password_len); + free(usr->password); + } + free(usr); + + return NULL; +} + +static void +srp_user_free(struct SRPUser *usr) +{ + if(!usr) + return; + + bnum_free(usr->a); + bnum_free(usr->A); + bnum_free(usr->S); + + free_ng(usr->ng); + + memset(usr->password, 0, usr->password_len); + + free(usr->username); + free(usr->password); + free((char *)usr->bytes_A); + + memset(usr, 0, sizeof(*usr)); + free(usr); +} + +static int +srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + +static const unsigned char * +srp_user_get_session_key(struct SRPUser *usr, int *key_length) +{ + if (key_length) + *key_length = usr->session_key_len; + return usr->session_key; +} + +/* Output: username, bytes_A, len_A */ +static void +srp_user_start_authentication(struct SRPUser *usr, const char **username, + const unsigned char **bytes_A, int *len_A) +{ + bnum_random(usr->a, 256); +// BN_hex2bn(&(usr->a), "D929DFB605687233C9E9030C2280156D03BDB9FDCF3CCE3BC27D9CCFCB5FF6A1"); + + bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); + + *len_A = bnum_num_bytes(usr->A); + *bytes_A = malloc(*len_A); + + if (!*bytes_A) + { + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); + + usr->bytes_A = *bytes_A; + *username = usr->username; +} + +/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ +static void +srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, + const unsigned char **bytes_M, int *len_M ) +{ + bnum s, B, k, v; + bnum tmp1, tmp2, tmp3; + bnum u, x; + + *len_M = 0; + *bytes_M = 0; + + bnum_bin2bn(s, bytes_s, len_s); + bnum_bin2bn(B, bytes_B, len_B); + k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g); + + bnum_new(v); + bnum_new(tmp1); + bnum_new(tmp2); + bnum_new(tmp3); + + if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) + goto cleanup1; + + u = H_nn_pad(usr->alg, usr->A, B); + x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len); + if (!u || !x) + goto cleanup2; + + // SRP-6a safety check + if (!bnum_is_zero(B) && !bnum_is_zero(u)) + { + bnum_modexp(v, usr->ng->g, x, usr->ng->N); + + // S = (B - k*(g^x)) ^ (a + ux) + bnum_mul(tmp1, u, x); + bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) + bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N); + bnum_mul(tmp3, k, tmp1); // tmp3 = k*(g^x) + bnum_sub(tmp1, B, tmp3); // tmp1 = (B - K*(g^x)) + bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); + + hash_num(usr->alg, usr->S, usr->session_key); + usr->session_key_len = hash_length(usr->alg); + + calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); + calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length(usr->alg); + } + else + { + *bytes_M = NULL; + if (len_M) + *len_M = 0; + } + + cleanup2: + bnum_free(x); + bnum_free(u); + cleanup1: + bnum_free(tmp3); + bnum_free(tmp2); + bnum_free(tmp1); + bnum_free(v); + bnum_free(k); + bnum_free(B); + bnum_free(s); +} + +static void +srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0) + usr->authenticated = 1; +} + + +/* ----------------------- SRP for the server side -------------------------- */ + +static int +srp_create_salted_verification_key(enum hash_alg alg, + SRP_NGType ng_type, const char *username, + const unsigned char *password, int len_password, + unsigned char **bytes_s, int *len_s, + unsigned char **bytes_v, int *len_v, + const char *n_hex, const char *g_hex ) +{ + bnum s, v, x; + NGConstant *ng; + + bnum_new(s); + bnum_new(v); + x = NULL; + + ng = new_ng(ng_type, n_hex, g_hex); + + *bytes_s = NULL; + *bytes_v = NULL; + + if (!s || !v || !ng) + goto error; + + bnum_random(s, 128); // MODIFIED from csrp's BN_rand(s, 32, -1, 0) + + x = calculate_x(alg, s, username, password, len_password); + if (!x) + goto error; + + bnum_modexp(v, ng->g, x, ng->N); + + *len_s = bnum_num_bytes(s); + *len_v = bnum_num_bytes(v); + + *bytes_s = malloc(*len_s); + *bytes_v = malloc(*len_v); + if (!(*bytes_s) || !(*bytes_v)) + goto error; + + bnum_bn2bin(s, (unsigned char *) *bytes_s, *len_s); + bnum_bn2bin(v, (unsigned char *) *bytes_v, *len_v); + + free_ng(ng); + bnum_free(s); + bnum_free(v); + bnum_free(x); + return 0; + + error: + free(*bytes_s); + free(*bytes_v); + free_ng(ng); + bnum_free(s); + bnum_free(v); + bnum_free(x); + return -1; +} + +static int +srp_verifier_start_authentication(enum hash_alg alg, SRP_NGType ng_type, + const unsigned char *bytes_v, int len_v, + unsigned char **bytes_b, int *len_b, + unsigned char **bytes_B, int *len_B, + const char *n_hex, const char *g_hex) +{ + bnum v, k, b, B, tmp1, tmp2; + NGConstant *ng; + + v = NULL; + k = NULL; + bnum_new(b); + bnum_new(B); + bnum_new(tmp1); + bnum_new(tmp2); + + *len_b = 0; + *bytes_b = NULL; + *len_B = 0; + *bytes_B = NULL; + + ng = new_ng(ng_type, n_hex, g_hex); + + if (!b || !B || !tmp1 || !tmp2 || !ng) + goto error; + + bnum_bin2bn(v, bytes_v, len_v); + + bnum_random(b, 256); // MODIFIED from BN_rand(b, 256, -1, 0) + + k = H_nn_pad(alg, ng->N, ng->g); // MODIFIED from H_nn(alg, ng->N, ng->g) + if (!k) + goto error; + + // B = kv + g^b + bnum_mul(tmp1, k, v); + bnum_modexp(tmp2, ng->g, b, ng->N); + bnum_modadd(B, tmp1, tmp2, ng->N); + + *len_B = bnum_num_bytes(B); + *len_b = bnum_num_bytes(b); + + *bytes_B = malloc(*len_B); + *bytes_b = malloc(*len_b); + if (!(*bytes_B) || !(*bytes_b)) + goto error; + + bnum_bn2bin(B, (unsigned char *) *bytes_B, *len_B); + bnum_bn2bin(b, (unsigned char *) *bytes_b, *len_b); + + bnum_free(b); + bnum_free(B); + bnum_free(v); + bnum_free(k); + bnum_free(tmp1); + bnum_free(tmp2); + free_ng(ng); + return 0; + + error: + free(*bytes_B); + free(*bytes_b); + bnum_free(b); + bnum_free(B); + bnum_free(v); + bnum_free(k); + bnum_free(tmp1); + bnum_free(tmp2); + free_ng(ng); + return -1; +} + +static void +srp_verifier_free(struct SRPVerifier *ver) +{ + if (!ver) + return; + + free_ng(ver->ng); + + free(ver->username); + + memset(ver, 0, sizeof(struct SRPVerifier)); + free(ver); +} + +static struct SRPVerifier * +srp_verifier_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_v, int len_v, + const unsigned char *bytes_A, int len_A, + const unsigned char *bytes_b, int len_b, + const unsigned char *bytes_B, int len_B, + const char *n_hex, const char *g_hex ) +{ + struct SRPVerifier *ver = NULL; + bnum s, v, A, b, B, S, tmp1, tmp2, u, k; + NGConstant *ng; + size_t ulen; + + bnum_bin2bn(s, bytes_s, len_s); + bnum_bin2bn(v, bytes_v, len_v); + bnum_bin2bn(A, bytes_A, len_A); + bnum_bin2bn(b, bytes_b, len_b); + bnum_bin2bn(B, bytes_B, len_B); + bnum_new(S); + bnum_new(tmp1); + bnum_new(tmp2); + u = NULL; + k = NULL; + + ng = new_ng(ng_type, n_hex, g_hex); + + if (!s || !v || !A || !B || !S || !b || !tmp1 || !tmp2 || !ng) + goto error; + + ver = calloc(1, sizeof(struct SRPVerifier)); + if (!ver) + goto error; + + ulen = strlen(username) + 1; + + ver->alg = alg; + ver->ng = ng; + + ver->username = malloc(ulen); + if (!ver->username) + goto error; + + memcpy(ver->username, username, ulen); + + ver->authenticated = 0; + + // SRP-6a safety check + bnum_mod(tmp1, A, ng->N); + if (bnum_is_zero(tmp1)) + goto error; + + k = H_nn_pad(alg, ng->N, ng->g); // MODIFIED from H_nn(alg, ng->N, ng->g) + u = H_nn_pad(alg, A, B); // MODIFIED from H_nn(alg, A, B) + + // S = (A *(v^u)) ^ b + bnum_modexp(tmp1, v, u, ng->N); + bnum_mul(tmp2, A, tmp1); + bnum_modexp(S, tmp2, b, ng->N); + + hash_num(alg, S, ver->session_key); + ver->session_key_len = hash_length(ver->alg); + + calculate_M(alg, ng, ver->M, username, s, A, B, ver->session_key, ver->session_key_len); + calculate_H_AMK(alg, ver->H_AMK, A, ver->M, ver->session_key, ver->session_key_len); + + ver->bytes_B = bytes_B; + + bnum_free(s); + bnum_free(v); + bnum_free(A); + bnum_free(u); + bnum_free(k); + bnum_free(B); + bnum_free(S); + bnum_free(b); + bnum_free(tmp1); + bnum_free(tmp2); + return ver; + + error: + srp_verifier_free(ver); + bnum_free(s); + bnum_free(v); + bnum_free(A); + bnum_free(u); + bnum_free(k); + bnum_free(B); + bnum_free(S); + bnum_free(b); + bnum_free(tmp1); + bnum_free(tmp2); + return NULL; +} + +// user_M must be exactly SHA512_DIGEST_LENGTH bytes in size +static void +srp_verifier_verify_session(struct SRPVerifier *ver, const unsigned char *user_M, const unsigned char **bytes_HAMK) +{ + if (memcmp(ver->M, user_M, hash_length(ver->alg)) == 0) + { + ver->authenticated = 1; + *bytes_HAMK = ver->H_AMK; + } + else + *bytes_HAMK = NULL; +} + +static const unsigned char * +srp_verifier_get_session_key(struct SRPVerifier *ver, int *key_length) +{ + if (key_length) + *key_length = hash_length(ver->alg); + + return ver->session_key; +} + +/* -------------------------------- HELPERS --------------------------------- */ + +static void +hexread(uint8_t *out, size_t out_len, const char *in) +{ + char hex[] = { 0, 0, 0 }; + int i; + + for (i = 0; i < out_len; i++, in+=2) + { + hex[0] = in[0]; + hex[1] = in[1]; + out[i] = strtol(hex, NULL, 16); + } +} + +static pair_tlv_values_t * +message_process(const uint8_t *data, size_t data_len, const char **errmsg) +{ + pair_tlv_values_t *response; + pair_tlv_t *error; + int ret; + + response = pair_tlv_new(); + if (!response) + { + *errmsg = "Out of memory\n"; + return NULL; + } + + ret = pair_tlv_parse(data, data_len, response); + if (ret < 0) + { + *errmsg = "Could not parse TLV\n"; + goto error; + } + +#ifdef DEBUG_PAIR + pair_tlv_debug(response); +#endif + + error = pair_tlv_get_value(response, TLVType_Error); + if (error) + { + if (error->value[0] == TLVError_Authentication) + *errmsg = "Device returned an authentication failure"; + else if (error->value[0] == TLVError_Backoff) + *errmsg = "Device told us to back off pairing attempts\n"; + else if (error->value[0] == TLVError_MaxPeers) + *errmsg = "Max peers trying to connect to device\n"; + else if (error->value[0] == TLVError_MaxTries) + *errmsg = "Max pairing attemps reached\n"; + else if (error->value[0] == TLVError_Unavailable) + *errmsg = "Device is unuavailble at this time\n"; + else + *errmsg = "Device is busy/returned unknown error\n"; + + goto error; + } + + return response; + + error: + pair_tlv_free(response); + return NULL; +} + +/* Executes SHA512 RFC 5869 extract + expand, writing a derived key to okm + + hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk); + hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len); +*/ +static int +hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key) +{ +#ifdef CONFIG_OPENSSL +#include + EVP_PKEY_CTX *pctx; + + if (okm_len > SHA512_DIGEST_LENGTH) + return -1; + if (! (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL))) + return -1; + if (EVP_PKEY_derive_init(pctx) <= 0) + goto error; + if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()) <= 0) + goto error; + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) <= 0) + goto error; + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0) + goto error; + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)) <= 0) + goto error; + if (EVP_PKEY_derive(pctx, okm, &okm_len) <= 0) + goto error; + + EVP_PKEY_CTX_free(pctx); + return 0; + + error: + EVP_PKEY_CTX_free(pctx); + return -1; +#elif CONFIG_GCRYPT + uint8_t prk[SHA512_DIGEST_LENGTH]; + gcry_md_hd_t hmac_handle; + + if (okm_len > SHA512_DIGEST_LENGTH) + return -1; // Below calculation not valid if output is larger than hash size + if (gcry_md_open(&hmac_handle, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR) + return -1; + if (gcry_md_setkey(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) != GPG_ERR_NO_ERROR) + goto error; + gcry_md_write(hmac_handle, ikm, ikm_len); + memcpy(prk, gcry_md_read(hmac_handle, 0), sizeof(prk)); + + gcry_md_reset(hmac_handle); + + if (gcry_md_setkey(hmac_handle, prk, sizeof(prk)) != GPG_ERR_NO_ERROR) + goto error; + gcry_md_write(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)); + gcry_md_putc(hmac_handle, 1); + + memcpy(okm, gcry_md_read(hmac_handle, 0), okm_len); + + gcry_md_close(hmac_handle); + return 0; + + error: + gcry_md_close(hmac_handle); + return -1; +#else + return -1; +#endif +} + +static int +encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) + goto error; + + if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary + goto error; + + if (ad_len > 0 && EVP_EncryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) + goto error; + + if (EVP_EncryptUpdate(ctx, cipher, &len, plain, plain_len) != 1) + goto error; + + assert(len == plain_len); + + if (EVP_EncryptFinal_ex(ctx, NULL, &len) != 1) + goto error; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, tag) != 1) + goto error; + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) + return -1; + + if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) + goto error; + + if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_encrypt(hd, cipher, plain_len, plain, plain_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_gettag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) + goto error; + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#else + return -1; +#endif +} + +static int +decrypt_chacha(uint8_t *plain, const uint8_t *cipher, size_t cipher_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) +{ +#ifdef CONFIG_OPENSSL + EVP_CIPHER_CTX *ctx; + int len; + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) + goto error; + + if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary + goto error; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag) != 1) + goto error; + + if (ad_len > 0 && EVP_DecryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) + goto error; + + if (EVP_DecryptUpdate(ctx, plain, &len, cipher, cipher_len) != 1) + goto error; + + if (EVP_DecryptFinal_ex(ctx, NULL, &len) != 1) + goto error; + + EVP_CIPHER_CTX_free(ctx); + return 0; + + error: + EVP_CIPHER_CTX_free(ctx); + return -1; +#elif CONFIG_GCRYPT + gcry_cipher_hd_t hd; + + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) + return -1; + + if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) + goto error; + + if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_decrypt(hd, plain, cipher_len, cipher, cipher_len) != GPG_ERR_NO_ERROR) + goto error; + + if (gcry_cipher_checktag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) + goto error; + + gcry_cipher_close(hd); + return 0; + + error: + gcry_cipher_close(hd); + return -1; +#else + return -1; +#endif +} + +static int +create_info(uint8_t *info, size_t *info_len, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len) +{ + if (a_len + b_len + c_len > *info_len) + return -1; + + *info_len = a_len + b_len + c_len; + memcpy(info, a, a_len); + memcpy(info + a_len, b, b_len); + memcpy(info + a_len + b_len, c, c_len); + + return 0; +} + +static int +create_and_sign_device_info(uint8_t *msg, size_t *msg_len, const char *device_id, uint8_t *device_x, size_t device_x_len, uint8_t *pk, size_t pk_len, uint8_t *sk) +{ + pair_tlv_values_t *tlv; + uint8_t device_info[256]; + size_t device_info_len; + size_t device_id_len; + uint8_t signature[crypto_sign_BYTES]; + int ret; + + device_id_len = strlen(device_id); + device_info_len = sizeof(device_info); + + ret = create_info(device_info, &device_info_len, device_x, device_x_len, (uint8_t *)device_id, device_id_len, pk, pk_len); + if (ret < 0) + return -1; + + crypto_sign_detached(signature, NULL, device_info, device_info_len, sk); + + tlv = pair_tlv_new(); + pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)device_id, device_id_len); + pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature)); + + ret = pair_tlv_format(tlv, msg, msg_len); + + pair_tlv_free(tlv); + return ret; +} + +static int +create_and_sign_accessory_info(uint8_t *msg, size_t *msg_len, uint8_t *server_pk, size_t server_pk_len, const char *accessory_id, uint8_t *client_pk, size_t client_pk_len, uint8_t *sk) +{ + pair_tlv_values_t *tlv; + uint8_t accessory_info[256]; + size_t accessory_info_len; + size_t accessory_id_len; + uint8_t signature[crypto_sign_BYTES]; + int ret; + + accessory_id_len = strlen(accessory_id); + accessory_info_len = sizeof(accessory_info); + + ret = create_info(accessory_info, &accessory_info_len, server_pk, server_pk_len, (uint8_t *)accessory_id, accessory_id_len, client_pk, client_pk_len); + if (ret < 0) + return -1; + + crypto_sign_detached(signature, NULL, accessory_info, accessory_info_len, sk); + + tlv = pair_tlv_new(); + pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)accessory_id, accessory_id_len); + pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature)); + + ret = pair_tlv_format(tlv, msg, msg_len); + + pair_tlv_free(tlv); + return ret; +} + +static int +verify_info(uint8_t *signature, uint8_t *pk, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len) +{ + uint8_t info[256]; + size_t info_len; + int ret; + + info_len = sizeof(info); + ret = create_info(info, &info_len, a, a_len, b, b_len, c, c_len); + if (ret < 0) + return -1; + + return crypto_sign_verify_detached(signature, info, info_len, pk); +} + + +/* ------------------------- CLIENT IMPLEMENTATION -------------------------- */ + +static int +client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + + if (sodium_init() == -1) + return -1; + + if (handle->type == &pair_client_homekit_normal) + { + if (!pin || strlen(pin) < 4) + return -1; + } + else if (handle->type == &pair_client_homekit_transient && !pin) + { + pin = "3939"; + } + + if (device_id && strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) + return -1; + + memcpy(sctx->pin, pin, sizeof(sctx->pin)); + + sctx->add_cb = add_cb; + sctx->add_cb_arg = cb_arg; + + if (device_id) + memcpy(sctx->device_id, device_id, strlen(device_id)); + + crypto_sign_keypair(sctx->public_key, sctx->private_key); + + return 0; +} + +static void +client_setup_free(struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + + srp_user_free(sctx->user); + + free(sctx->pkB); + free(sctx->M2); + free(sctx->salt); + free(sctx->epk); + free(sctx->authtag); +} + +static uint8_t * +client_setup_request1(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *request; + uint8_t *data; + size_t data_len; + uint8_t method; + uint8_t flags; + int endian_test = 1; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = pair_tlv_new(); + + // Test here instead of setup_new() so we can give an error message + if(*(char *)&endian_test != 1) + { + handle->errmsg = "Setup request 1: No support for big endian architechture"; + goto error; + } + + sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0); + if (!sctx->user) + { + handle->errmsg = "Setup request 1: Create SRP user failed"; + goto error; + } + + method = PairingMethodPairSetup; + pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG01].state, sizeof(pair_keys_map[PAIR_SETUP_MSG01].state)); + pair_tlv_add_value(request, TLVType_Method, &method, sizeof(method)); + + if (handle->type == &pair_client_homekit_transient) + { + flags = PairingFlagsTransient; + pair_tlv_add_value(request, TLVType_Flags, &flags, sizeof(flags)); + } + + ret = pair_tlv_format(request, data, &data_len); + if (ret < 0) + { + handle->errmsg = "Setup request 1: pair_tlv_format returned an error"; + goto error; + } + + *len = data_len; + + pair_tlv_free(request); + return data; + + error: + pair_tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +client_setup_request2(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *request; + uint8_t *data; + size_t data_len; + const char *auth_username = NULL; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = pair_tlv_new(); + + // Calculate A + srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len); + + // Calculate M1 (client proof) + srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len); + + pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG03].state, sizeof(pair_keys_map[PAIR_SETUP_MSG03].state)); + pair_tlv_add_value(request, TLVType_PublicKey, sctx->pkA, sctx->pkA_len); + pair_tlv_add_value(request, TLVType_Proof, sctx->M1, sctx->M1_len); + + ret = pair_tlv_format(request, data, &data_len); + if (ret < 0) + { + handle->errmsg = "Setup request 2: pair_tlv_format returned an error"; + goto error; + } + + *len = data_len; + + pair_tlv_free(request); + return data; + + error: + pair_tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +client_setup_request3(size_t *len, struct pair_setup_context *handle) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *request; + uint8_t *data; + size_t data_len; + const unsigned char *session_key; + int session_key_len; + uint8_t device_x[32]; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + pair_tlv_values_t *append; + size_t append_len; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = pair_tlv_new(); + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + handle->errmsg = "Setup request 3: No valid session key"; + goto error; + } + + ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN); + if (ret < 0) + { + handle->errmsg = "Setup request 3: hkdf error getting device_x"; + goto error; + } + + ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key); + if (ret < 0) + { + handle->errmsg = "Setup request 3: error creating signed device info"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG05); + if (ret < 0) + { + handle->errmsg = "Setup request 3: hkdf error getting derived_key"; + goto error; + } + + // Append TLV-encoded public key to *data, which already has identifier and signature + append = pair_tlv_new(); + append_len = REQUEST_BUFSIZE - data_len; + pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key)); + ret = pair_tlv_format(append, data + data_len, &append_len); + pair_tlv_free(append); + if (ret < 0) + { + handle->errmsg = "Setup request 3: error appending public key to TLV"; + goto error; + } + data_len += append_len; + + memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG05].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + handle->errmsg = "Setup request 3: Could not encrypt"; + goto error; + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG05].state, sizeof(pair_keys_map[PAIR_SETUP_MSG05].state)); + pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format + ret = pair_tlv_format(request, data, &data_len); + if (ret < 0) + { + handle->errmsg = "Setup request 3: error appending public key to TLV"; + goto error; + } + + *len = data_len; + + free(encrypted_data); + pair_tlv_free(request); + return data; + + error: + free(encrypted_data); + pair_tlv_free(request); + free(data); + return NULL; +} + +static int +client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *response; + pair_tlv_t *pk; + pair_tlv_t *salt; + + response = message_process(data, data_len, &handle->errmsg); + if (!response) + { + return -1; + } + + pk = pair_tlv_get_value(response, TLVType_PublicKey); + salt = pair_tlv_get_value(response, TLVType_Salt); + if (!pk || !salt) + { + handle->errmsg = "Setup response 1: Missing or invalid pk/salt"; + goto error; + } + + sctx->pkB_len = pk->size; // 384 + sctx->pkB = malloc(sctx->pkB_len); + memcpy(sctx->pkB, pk->value, sctx->pkB_len); + + sctx->salt_len = salt->size; // 16 + sctx->salt = malloc(sctx->salt_len); + memcpy(sctx->salt, salt->value, sctx->salt_len); + + pair_tlv_free(response); + return 0; + + error: + pair_tlv_free(response); + return -1; +} + +static int +client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *response; + pair_tlv_t *proof; + const uint8_t *session_key; + int session_key_len; + + response = message_process(data, data_len, &handle->errmsg); + if (!response) + { + return -1; + } + + proof = pair_tlv_get_value(response, TLVType_Proof); + if (!proof) + { + handle->errmsg = "Setup response 2: Missing proof"; + goto error; + } + + sctx->M2_len = proof->size; // 64 + sctx->M2 = malloc(sctx->M2_len); + memcpy(sctx->M2, proof->value, sctx->M2_len); + + // Check M2 + srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2); + if (!srp_user_is_authenticated(sctx->user)) + { + handle->errmsg = "Setup response 2: Server authentication failed"; + goto error; + } + + if (handle->type == &pair_client_homekit_transient) + { + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + handle->errmsg = "Setup response 2: Could not compute session key"; + goto error; + } + + assert(sizeof(handle->result.shared_secret) >= session_key_len); + + memcpy(handle->result.shared_secret, session_key, session_key_len); + handle->result.shared_secret_len = session_key_len; + + handle->status = PAIR_STATUS_COMPLETED; + } + + pair_tlv_free(response); + return 0; + + error: + pair_tlv_free(response); + return -1; +} + +static int +client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_setup_context *sctx = &handle->sctx.client; + pair_tlv_values_t *response; + pair_tlv_t *encrypted_data; + pair_tlv_t *device_id; + pair_tlv_t *pk; + pair_tlv_t *signature; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + const uint8_t *session_key; + int session_key_len; + uint8_t device_x[32]; + int ret; + + response = message_process(data, data_len, &handle->errmsg); + if (!response) + { + return -1; + } + + encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData); + if (!encrypted_data) + { + handle->errmsg = "Setup response 3: Missing encrypted_data"; + goto error; + } + + session_key = srp_user_get_session_key(sctx->user, &session_key_len); + if (!session_key) + { + handle->errmsg = "Setup response 3: No valid session key"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG06); + if (ret < 0) + { + handle->errmsg = "Setup response 3: hkdf error getting derived_key"; + goto error; + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + handle->errmsg = "Setup response 3: Invalid encrypted data"; + goto error; + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG06].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + handle->errmsg = "Setup response 3: Decryption error"; + goto error; + } + + pair_tlv_free(response); + response = message_process(decrypted_data, encrypted_len, &handle->errmsg); + if (!response) + { + goto error; + } + + ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN); + if (ret < 0) + { + handle->errmsg = "Setup response 3: hkdf error getting device_x"; + goto error; + } + + device_id = pair_tlv_get_value(response, TLVType_Identifier); + pk = pair_tlv_get_value(response, TLVType_PublicKey); + signature = pair_tlv_get_value(response, TLVType_Signature); + if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES) + { + handle->errmsg = "Setup response 3: Missing/invalid device ID, public key or signature"; + goto error; + } + + ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size); + if (ret < 0) + { + handle->errmsg = "Setup request 3: Invalid signature"; + goto error; + } + + assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key)); + assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key)); + assert(sizeof(handle->result.server_public_key) == pk->size); + + memcpy(handle->result.client_private_key, sctx->private_key, sizeof(sctx->private_key)); + memcpy(handle->result.client_public_key, sctx->public_key, sizeof(sctx->public_key)); + memcpy(handle->result.server_public_key, pk->value, pk->size); + memcpy(handle->result.device_id, device_id->value, device_id->size); + + if (sctx->add_cb) + sctx->add_cb(handle->result.server_public_key, handle->result.device_id, sctx->add_cb_arg); + + handle->status = PAIR_STATUS_COMPLETED; + + free(decrypted_data); + pair_tlv_free(response); + return 0; + + error: + free(decrypted_data); + pair_tlv_free(response); + return -1; +} + +static int +client_setup_result(struct pair_setup_context *handle) +{ + char *ptr; + int i; + + assert(sizeof(handle->result_str) >= 2 * sizeof(handle->result.client_private_key) + 2 * sizeof(handle->result.server_public_key) + 1); + + // It is enough to export the private key, since the public key can be + // extracted from that with crypto_sign_ed25519_sk_to_pk (it is the last 32 + // bytes) + ptr = handle->result_str; + for (i = 0; i < sizeof(handle->result.client_private_key); i++) + ptr += sprintf(ptr, "%02x", handle->result.client_private_key[i]); // 2 x 64 bytes + for (i = 0; i < sizeof(handle->result.server_public_key); i++) + ptr += sprintf(ptr, "%02x", handle->result.server_public_key[i]); // 2 x 32 bytes + *ptr = '\0'; + + return 0; +} + +static int +client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + size_t hexkey_len; + + if (sodium_init() == -1) + return -1; + + if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) + return -1; + + if (!client_setup_keys) + return -1; + + hexkey_len = strlen(client_setup_keys); + if (hexkey_len == 2 * sizeof(vctx->client_private_key) + 2 * sizeof(vctx->server_public_key)) + { + hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys); + hexread(vctx->server_public_key, sizeof(vctx->server_public_key), client_setup_keys + 2 * sizeof(vctx->client_private_key)); + vctx->verify_server_signature = true; + } + else if (hexkey_len == 2 * sizeof(vctx->client_private_key)) // No server public key known, so signature validation will be skipped + { + hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys); + } + else + return -1; + + crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key); + + snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id); + + return 0; +} + +static uint8_t * +client_verify_request1(size_t *len, struct pair_verify_context *handle) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; +// const uint8_t basepoint[crypto_scalarmult_BYTES] = {9}; // 32 bytes + pair_tlv_values_t *request; + uint8_t *data; + size_t data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = pair_tlv_new(); + + crypto_box_keypair(vctx->client_eph_public_key, vctx->client_eph_private_key); + +/* + // TODO keep around in case box_keypair doesn't work + ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint); + if (ret < 0) + { + handle->errmsg = "Verify request 1: Curve 25519 returned an error"; + goto error; + } +*/ + + pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG01].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG01].state)); + pair_tlv_add_value(request, TLVType_PublicKey, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + + ret = pair_tlv_format(request, data, &data_len); + if (ret < 0) + { + handle->errmsg = "Verify request 1: pair_tlv_format returned an error"; + goto error; + } + + *len = data_len; + + pair_tlv_free(request); + return data; + + error: + pair_tlv_free(request); + free(data); + return NULL; +} + +static uint8_t * +client_verify_request2(size_t *len, struct pair_verify_context *handle) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + pair_tlv_values_t *request; + uint8_t *data; + size_t data_len; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + request = pair_tlv_new(); + + ret = create_and_sign_device_info(data, &data_len, vctx->device_id, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), + vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->client_private_key); + if (ret < 0) + { + handle->errmsg = "Verify request 2: error creating signed device info"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG03); + if (ret < 0) + { + handle->errmsg = "Verify request 2: hkdf error getting derived_key"; + goto error; + } + + memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG03].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + handle->errmsg = "Verify request 2: Could not encrypt"; + goto error; + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG03].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG03].state)); + pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format + ret = pair_tlv_format(request, data, &data_len); + if (ret < 0) + { + handle->errmsg = "Verify request 2: pair_tlv_format returned an error"; + goto error; + } + + *len = data_len; + + free(encrypted_data); + pair_tlv_free(request); + return data; + + error: + free(encrypted_data); + pair_tlv_free(request); + free(data); + return NULL; +} + +static int +client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + pair_tlv_values_t *response; + pair_tlv_t *encrypted_data; + pair_tlv_t *public_key; + pair_tlv_t *device_id; + pair_tlv_t *signature; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + int ret; + + response = message_process(data, data_len, &handle->errmsg); + if (!response) + { + return -1; + } + + encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData); + if (!encrypted_data) + { + handle->errmsg = "Verify response 1: Missing encrypted_data"; + goto error; + } + + public_key = pair_tlv_get_value(response, TLVType_PublicKey); + if (!public_key || public_key->size != sizeof(vctx->server_eph_public_key)) + { + handle->errmsg = "Verify response 1: Missing or invalid public_key"; + goto error; + } + + memcpy(vctx->server_eph_public_key, public_key->value, sizeof(vctx->server_eph_public_key)); + ret = crypto_scalarmult(vctx->shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key); + if (ret < 0) + { + handle->errmsg = "Verify response 1: Curve 25519 returned an error"; + goto error; + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG02); + if (ret < 0) + { + handle->errmsg = "Verify response 1: hkdf error getting derived_key"; + goto error; + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + handle->errmsg = "Verify response 1: Invalid encrypted data"; + goto error; + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG02].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + handle->errmsg = "Verify response 1: Decryption error"; + goto error; + } + + pair_tlv_free(response); + response = message_process(decrypted_data, encrypted_len, &handle->errmsg); + if (!response) + { + goto error; + } + + device_id = pair_tlv_get_value(response, TLVType_Identifier); + signature = pair_tlv_get_value(response, TLVType_Signature); + if (!device_id || !signature || signature->size != crypto_sign_BYTES) + { + handle->errmsg = "Verify response 1: Missing device ID or signature"; + goto error; + } + + if (vctx->verify_server_signature) + { + ret = verify_info(signature->value, vctx->server_public_key, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), + device_id->value, device_id->size, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); + if (ret < 0) + { + handle->errmsg = "Verify response 1: Invalid signature"; + goto error; + } + } + + free(decrypted_data); + pair_tlv_free(response); + return 0; + + error: + free(decrypted_data); + pair_tlv_free(response); + return -1; +} + +static int +client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_client_verify_context *vctx = &handle->vctx.client; + pair_tlv_values_t *response; + + response = message_process(data, data_len, &handle->errmsg); + if (!response) + { + return -1; + } + + memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret)); + handle->result.shared_secret_len = sizeof(vctx->shared_secret); + + handle->status = PAIR_STATUS_COMPLETED; + + return 0; +} + + +/* ------------------------- SERVER IMPLEMENTATION -------------------------- */ + +// Use (unsecure) keys seeded from device_id. We need the keys to always be the +// same during pair setup and pair verify, since the client saves them after +// pair-setup 3, so that the signature in pair-verify 1 can be checked. +static void +server_keypair(uint8_t *public_key, uint8_t *private_key, const char *device_id) +{ + uint8_t seed[crypto_sign_SEEDBYTES] = { 0 }; + size_t len; + + len = strlen(device_id); + if (len > sizeof(seed)) + len = sizeof(seed); + + memcpy(seed, device_id, len); + + crypto_sign_seed_keypair(public_key, private_key, seed); +} + +static uint8_t * +server_auth_failed_response(size_t *len, enum pair_keys msg_state) +{ + pair_tlv_values_t *response; + uint8_t *data; + size_t data_len; + uint8_t error = TLVError_Authentication; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + pair_tlv_add_value(response, TLVType_Error, &error, sizeof(error)); + + pair_tlv_format(response, data, &data_len); + pair_tlv_free(response); + + *len = data_len; + + return data; +} + +static int +server_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + + if (sodium_init() == -1) + return -1; + + if (!pin) + pin = "3939"; + + if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) + return -1; + + memcpy(sctx->pin, pin, sizeof(sctx->pin)); + + sctx->add_cb = add_cb; + sctx->add_cb_arg = cb_arg; + + snprintf(sctx->device_id, sizeof(sctx->device_id), "%s", device_id); + + server_keypair(sctx->public_key, sctx->private_key, sctx->device_id); + + return 0; +} + +static void +server_setup_free(struct pair_setup_context *handle) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + + srp_verifier_free(sctx->verifier); + + free(sctx->pkA); + free(sctx->pkB); + free(sctx->b); + free(sctx->M1); + free(sctx->v); + free(sctx->salt); +} + +static int +server_setup_request1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; +// enum pair_keys msg_state = PAIR_SETUP_MSG01; + pair_tlv_values_t *request; + pair_tlv_t *method; + pair_tlv_t *type; + int ret; + + request = message_process(data, data_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + method = pair_tlv_get_value(request, TLVType_Method); + if (!method || method->size != 1 || method->value[0] != 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Missing or unexpected pairing method in TLV"); + } + + type = pair_tlv_get_value(request, TLVType_Flags); + sctx->is_transient = (type && type->size == 1 && type->value[0] == PairingFlagsTransient); + + // Note this is modified to return a 16 byte salt + ret = srp_create_salted_verification_key(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), + &sctx->salt, &sctx->salt_len, &sctx->v, &sctx->v_len, NULL, NULL); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not create verification key"); + } + + ret = srp_verifier_start_authentication(HASH_SHA512, SRP_NG_3072, sctx->v, sctx->v_len, + &sctx->b, &sctx->b_len, &sctx->pkB, &sctx->pkB_len, NULL, NULL); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not compute B"); + } + + pair_tlv_free(request); + return 0; + + error: + pair_tlv_free(request); + return -1; +} + +static int +server_setup_request2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; +// enum pair_keys msg_state = PAIR_SETUP_MSG03; + pair_tlv_values_t *request; + pair_tlv_t *pk; + pair_tlv_t *proof; + + request = message_process(data, data_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + pk = pair_tlv_get_value(request, TLVType_PublicKey); + proof = pair_tlv_get_value(request, TLVType_Proof); + if (!pk || !proof) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Missing pkA or proof"); + } + + sctx->pkA_len = pk->size; // 384 + sctx->pkA = malloc(sctx->pkA_len); + memcpy(sctx->pkA, pk->value, sctx->pkA_len); + + sctx->M1_len = proof->size; // 64 + sctx->M1 = malloc(sctx->M1_len); + memcpy(sctx->M1, proof->value, sctx->M1_len); + + sctx->verifier = srp_verifier_new(HASH_SHA512, SRP_NG_3072, USERNAME, sctx->salt, sctx->salt_len, sctx->v, sctx->v_len, + sctx->pkA, sctx->pkA_len, sctx->b, sctx->b_len, sctx->pkB, sctx->pkB_len, NULL, NULL); + if (!sctx->verifier) + { + handle->status = PAIR_STATUS_AUTH_FAILED; + goto out; + } + + sctx->M2_len = 64; // 512 bit hash + srp_verifier_verify_session(sctx->verifier, sctx->M1, &sctx->M2); + if (!sctx->M2) + { + handle->status = PAIR_STATUS_AUTH_FAILED; + goto out; // Not an error, server should give proper TLV-formatet reply + } + + out: + pair_tlv_free(request); + return 0; + + error: + pair_tlv_free(request); + return -1; +} + +static int +server_setup_request3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + enum pair_keys msg_state = PAIR_SETUP_MSG05; + pair_tlv_values_t *request; + pair_tlv_t *encrypted_data; + pair_tlv_t *device_id; + pair_tlv_t *pk; + pair_tlv_t *signature; + const uint8_t *session_key; + int session_key_len; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + uint8_t device_x[32]; + int ret; + + request = message_process(data, data_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); + if (!session_key) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: No valid session key"); + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting derived_key"); + } + + encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData); + if (!encrypted_data) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing encrypted_data"); + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Invalid encrypted data"); + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Decryption error"); + } + + pair_tlv_free(request); + request = message_process(decrypted_data, encrypted_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting device_x"); + } + + device_id = pair_tlv_get_value(request, TLVType_Identifier); + pk = pair_tlv_get_value(request, TLVType_PublicKey); + signature = pair_tlv_get_value(request, TLVType_Signature); + if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing/invalid device ID, public key or signature"); + } + + ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size); + if (ret < 0) + { + handle->status = PAIR_STATUS_AUTH_FAILED; + goto out; + } + + memcpy(handle->result.device_id, device_id->value, device_id->size); + memcpy(handle->result.client_public_key, pk->value, pk->size); + + out: + free(decrypted_data); + pair_tlv_free(request); + return 0; + + error: + free(decrypted_data); + pair_tlv_free(request); + return -1; +} + +static uint8_t * +server_setup_response1(size_t *len, struct pair_setup_context *handle) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + enum pair_keys msg_state = PAIR_SETUP_MSG02; + pair_tlv_values_t *response; + uint8_t *data; + size_t data_len; + int ret; + + if (handle->status == PAIR_STATUS_AUTH_FAILED) + return server_auth_failed_response(len, msg_state); + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + pair_tlv_add_value(response, TLVType_Salt, sctx->salt, sctx->salt_len); // 16 + pair_tlv_add_value(response, TLVType_PublicKey, sctx->pkB, sctx->pkB_len); // 384 + + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 1: pair_tlv_format returned an error"); + } + + *len = data_len; + + pair_tlv_free(response); + return data; + + error: + free(data); + pair_tlv_free(response); + return NULL; +} + +static uint8_t * +server_setup_response2(size_t *len, struct pair_setup_context *handle) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + enum pair_keys msg_state = PAIR_SETUP_MSG04; + pair_tlv_values_t *response; + uint8_t *data; + size_t data_len; + const uint8_t *session_key; + int session_key_len; + int ret; + + if (handle->status == PAIR_STATUS_AUTH_FAILED) + return server_auth_failed_response(len, msg_state); + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + pair_tlv_add_value(response, TLVType_Proof, sctx->M2, sctx->M2_len); // 384 + + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 2: pair_tlv_format returned an error"); + } + + if (sctx->is_transient) + { + session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); + if (!session_key) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Could not compute session key"); + } + + assert(sizeof(handle->result.shared_secret) >= session_key_len); + + memcpy(handle->result.shared_secret, session_key, session_key_len); + handle->result.shared_secret_len = session_key_len; + + handle->status = PAIR_STATUS_COMPLETED; + } + + *len = data_len; + + pair_tlv_free(response); + return data; + + error: + free(data); + pair_tlv_free(response); + return NULL; +} + +static uint8_t * +server_setup_response3(size_t *len, struct pair_setup_context *handle) +{ + struct pair_server_setup_context *sctx = &handle->sctx.server; + enum pair_keys msg_state = PAIR_SETUP_MSG06; + const uint8_t *session_key; + int session_key_len; + pair_tlv_values_t *response; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + pair_tlv_values_t *append; + size_t append_len; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + uint8_t *data; + size_t data_len; + uint8_t device_x[32]; + int ret; + + if (handle->status == PAIR_STATUS_AUTH_FAILED) + return server_auth_failed_response(len, msg_state); + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); + if (!session_key) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: No valid session key"); + } + + ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting device_x"); + } + + ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: create device info returned an error"); + } + + // Append TLV-encoded public key to *data, which already has identifier and signature + append = pair_tlv_new(); + append_len = REQUEST_BUFSIZE - data_len; + pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key)); + ret = pair_tlv_format(append, data + data_len, &append_len); + pair_tlv_free(append); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV"); + } + data_len += append_len; + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting derived_key"); + } + + memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: Could not encrypt"); + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV"); + } + + if (sctx->add_cb) + sctx->add_cb(handle->result.client_public_key, handle->result.device_id, sctx->add_cb_arg); + + handle->status = PAIR_STATUS_COMPLETED; + + *len = data_len; + + free(encrypted_data); + pair_tlv_free(response); + return data; + + error: + free(encrypted_data); + free(data); + pair_tlv_free(response); + return NULL; +} + + +static int +server_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) +{ + struct pair_server_verify_context *vctx = &handle->vctx.server; + + if (sodium_init() == -1) + return -1; + + if (client_setup_keys) + return -1; + + if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) + return -1; + + snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id); + + vctx->get_cb = cb; + vctx->get_cb_arg = cb_arg; + vctx->verify_client_signature = cb; + + server_keypair(vctx->server_public_key, vctx->server_private_key, vctx->device_id); + + return 0; +} + +static int +server_verify_request1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_server_verify_context *vctx = &handle->vctx.server; +// enum pair_keys msg_state = PAIR_VERIFY_MSG01; + pair_tlv_values_t *request; + pair_tlv_t *pk; + + request = message_process(data, data_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + pk = pair_tlv_get_value(request, TLVType_PublicKey); + if (!pk || pk->size != sizeof(vctx->client_eph_public_key)) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 1: Missing or invalid public_key"); + } + + memcpy(vctx->client_eph_public_key, pk->value, sizeof(vctx->client_eph_public_key)); + + pair_tlv_free(request); + return 0; + + error: + pair_tlv_free(request); + return -1; +} + +static int +server_verify_request2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) +{ + struct pair_server_verify_context *vctx = &handle->vctx.server; + enum pair_keys msg_state = PAIR_VERIFY_MSG03; + pair_tlv_values_t *request; + pair_tlv_t *encrypted_data; + pair_tlv_t *device_id; + pair_tlv_t *signature; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + size_t encrypted_len; + uint8_t *decrypted_data = NULL; + char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 }; + uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; + int ret; + + request = message_process(data, data_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: hkdf error getting derived_key"); + } + + encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData); + if (!encrypted_data) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing encrypted_data"); + } + + // encrypted_data->value consists of the encrypted payload + the auth tag + if (encrypted_data->size < AUTHTAG_LENGTH) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Invalid encrypted data"); + } + + encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; + memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); + memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); + + decrypted_data = malloc(encrypted_len); + + ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Decryption error"); + } + + pair_tlv_free(request); + request = message_process(decrypted_data, encrypted_len, &handle->errmsg); + if (!request) + { + RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); + } + + device_id = pair_tlv_get_value(request, TLVType_Identifier); + signature = pair_tlv_get_value(request, TLVType_Signature); + if (!device_id || !signature || signature->size != crypto_sign_BYTES) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing identifier or signature"); + } + + if (vctx->verify_client_signature) + { + if (device_id->size >= sizeof(id_str)) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Device ID from peer is too long"); + } + + memcpy(id_str, device_id->value, device_id->size); + + ret = vctx->get_cb(client_public_key, id_str, vctx->get_cb_arg); + if (ret < 0) + { + handle->status = PAIR_STATUS_AUTH_FAILED; + goto out; + } + + ret = verify_info(signature->value, client_public_key, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), + device_id->value, device_id->size, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); + if (ret < 0) + { + handle->status = PAIR_STATUS_AUTH_FAILED; + goto out; + } + } + + out: + free(decrypted_data); + pair_tlv_free(request); + return 0; + + error: + free(decrypted_data); + pair_tlv_free(request); + return -1; +} + +static uint8_t * +server_verify_response1(size_t *len, struct pair_verify_context *handle) +{ + struct pair_server_verify_context *vctx = &handle->vctx.server; + enum pair_keys msg_state = PAIR_VERIFY_MSG02; + pair_tlv_values_t *response; + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t derived_key[32]; + uint8_t *encrypted_data = NULL; + size_t encrypted_data_len; + uint8_t *data; + size_t data_len; + int ret; + + if (handle->status == PAIR_STATUS_AUTH_FAILED) + return server_auth_failed_response(len, msg_state); + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + crypto_box_keypair(vctx->server_eph_public_key, vctx->server_eph_private_key); + + ret = crypto_scalarmult(vctx->shared_secret, vctx->server_eph_private_key, vctx->client_eph_public_key); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error generating shared secret"); + } + + ret = create_and_sign_accessory_info(data, &data_len, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->device_id, + vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), vctx->server_private_key); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error creating device info"); + } + + ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: hkdf error getting derived_key"); + } + + memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); + + encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag + encrypted_data = malloc(encrypted_data_len); + + ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Could not encrypt"); + } + + memcpy(encrypted_data + data_len, tag, sizeof(tag)); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + pair_tlv_add_value(response, TLVType_PublicKey, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); + pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len); + + data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: pair_tlv_format returned an error"); + } + + *len = data_len; + + free(encrypted_data); + pair_tlv_free(response); + return data; + + error: + free(encrypted_data); + free(data); + pair_tlv_free(response); + return NULL; +} + +static uint8_t * +server_verify_response2(size_t *len, struct pair_verify_context *handle) +{ + struct pair_server_verify_context *vctx = &handle->vctx.server; + enum pair_keys msg_state = PAIR_VERIFY_MSG04; + pair_tlv_values_t *response; + uint8_t *data; + size_t data_len; + int ret; + + if (handle->status == PAIR_STATUS_AUTH_FAILED) + return server_auth_failed_response(len, msg_state); + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); + + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 2: pair_tlv_format returned an error"); + } + + *len = data_len; + + memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret)); + handle->result.shared_secret_len = sizeof(vctx->shared_secret); + + handle->status = PAIR_STATUS_COMPLETED; + + pair_tlv_free(response); + return data; + + error: + free(data); + pair_tlv_free(response); + return NULL; +} + +static int +server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) +{ + const char *errmsg; + pair_tlv_values_t *request; + pair_tlv_t *device_id; + pair_tlv_t *pk; + char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 }; + + request = message_process(in, in_len, &errmsg); + if (!request) + { + goto error; + } + + device_id = pair_tlv_get_value(request, TLVType_Identifier); + pk = pair_tlv_get_value(request, TLVType_PublicKey); + if (!device_id || device_id->size >= sizeof(id_str) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES) + { + goto error; + } + + memcpy(id_str, device_id->value, device_id->size); + + cb(pk->value, id_str, cb_arg); + + pair_tlv_free(request); + return 0; + + error: + pair_tlv_free(request); + return -1; +} + +static uint8_t * +server_add_remove_response(size_t *len) +{ + pair_tlv_values_t *response; + uint8_t *data; + uint8_t state = 2; + size_t data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &state, sizeof(state)); + + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + goto error; + } + + *len = data_len; + + pair_tlv_free(response); + return data; + + error: + free(data); + pair_tlv_free(response); + return NULL; +} + +static int +server_add_remove(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) +{ + int ret; + + ret = server_add_remove_request(cb, cb_arg, in, in_len); + if (ret < 0) + return -1; + + *out = server_add_remove_response(out_len); + if (!*out) + return -1; + + return 0; +} + +static int +server_list_cb(uint8_t public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id, void *cb_arg) +{ + pair_tlv_values_t *response = cb_arg; + + pair_tlv_add_value(response, TLVType_Identifier, (unsigned char *)device_id, strlen(device_id)); + pair_tlv_add_value(response, TLVType_PublicKey, public_key, crypto_sign_PUBLICKEYBYTES); + return 0; +} + +static uint8_t * +server_list_response(size_t *len, pair_list_cb cb, void *cb_arg) +{ + pair_tlv_values_t *response; + uint8_t *data; + uint8_t state = 2; + size_t data_len; + int ret; + + data_len = REQUEST_BUFSIZE; + data = malloc(data_len); + response = pair_tlv_new(); + + pair_tlv_add_value(response, TLVType_State, &state, sizeof(state)); + + cb(server_list_cb, response, cb_arg); + + ret = pair_tlv_format(response, data, &data_len); + if (ret < 0) + { + goto error; + } + + *len = data_len; + + pair_tlv_free(response); + return data; + + error: + free(data); + pair_tlv_free(response); + return NULL; +} + +static int +server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) +{ + // Skip reading the request, it just has state = 1 and pair method = + // PairingMethodListPairings + + *out = server_list_response(out_len, cb, cb_arg); + if (!*out) + return -1; + + return 0; +} + + +/* ----------------------- CIPHERING IMPLEMENTATION ------------------------- */ + +static void +cipher_free(struct pair_cipher_context *cctx) +{ + if (!cctx) + return; + + free(cctx); +} + +static struct pair_cipher_context * +cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len) +{ + struct pair_cipher_context *cctx; + enum pair_keys write_key; + enum pair_keys read_key; + int ret; + + // Note that events is opposite, probably because it is a reverse connection + switch (channel) + { + case 0: + write_key = PAIR_CONTROL_WRITE; + read_key = PAIR_CONTROL_READ; + break; + case 1: + write_key = PAIR_EVENTS_READ; + read_key = PAIR_EVENTS_WRITE; + break; + case 2: + write_key = PAIR_CONTROL_READ; + read_key = PAIR_CONTROL_WRITE; + break; + case 3: + write_key = PAIR_EVENTS_WRITE; + read_key = PAIR_EVENTS_READ; + break; + default: + return NULL; + } + + cctx = calloc(1, sizeof(struct pair_cipher_context)); + if (!cctx) + goto error; + + cctx->type = type; + + ret = hkdf_extract_expand(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_key); + if (ret < 0) + goto error; + + ret = hkdf_extract_expand(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_key); + if (ret < 0) + goto error; + + return cctx; + + error: + pair_cipher_free(cctx); + return NULL; +} + +static ssize_t +encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) +{ + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + const uint8_t *plain_block; + uint8_t *cipher_block; + uint16_t block_len; + int nblocks; + int ret; + int i; + + if (plaintext_len == 0 || !plaintext) + return -1; + + // Encryption is done in blocks, where each block consists of a short, the + // encrypted data and an auth tag. The short is the size of the encrypted + // data. The encrypted data in the block cannot exceed ENCRYPTED_LEN_MAX. + nblocks = 1 + ((plaintext_len - 1) / ENCRYPTED_LEN_MAX); // Ceiling of division + + *ciphertext_len = nblocks * (sizeof(block_len) + AUTHTAG_LENGTH) + plaintext_len; + *ciphertext = malloc(*ciphertext_len); + + cctx->encryption_counter_prev = cctx->encryption_counter; + + for (i = 0, plain_block = plaintext, cipher_block = *ciphertext; i < nblocks; i++) + { + // If it is the last block we will encrypt only the remaining data + block_len = (i + 1 == nblocks) ? (plaintext + plaintext_len - plain_block) : ENCRYPTED_LEN_MAX; + + memcpy(nonce + 4, &(cctx->encryption_counter), sizeof(cctx->encryption_counter));// TODO BE or LE? + + // Write the ciphered block + memcpy(cipher_block, &block_len, sizeof(block_len)); // TODO BE or LE? + ret = encrypt_chacha(cipher_block + sizeof(block_len), plain_block, block_len, cctx->encryption_key, sizeof(cctx->encryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); + if (ret < 0) + { + cctx->errmsg = "Encryption with chacha poly1305 failed"; + cctx->encryption_counter = cctx->encryption_counter_prev; + free(*ciphertext); + return -1; + } + memcpy(cipher_block + sizeof(block_len) + block_len, tag, AUTHTAG_LENGTH); + + plain_block += block_len; + cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; + cctx->encryption_counter++; + } + +#ifdef DEBUG_PAIR + hexdump("Encrypted:\n", *ciphertext, *ciphertext_len); +#endif + + return plain_block - plaintext; +} + +static ssize_t +decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) +{ + uint8_t nonce[NONCE_LENGTH] = { 0 }; + uint8_t tag[AUTHTAG_LENGTH]; + uint8_t *plain_block; + const uint8_t *cipher_block; + uint16_t block_len; + int ret; + + if (ciphertext_len < sizeof(block_len) || !ciphertext) + return -1; + + // This will allocate more than we need. Since we don't know the number of + // blocks in the ciphertext yet we can't calculate the exact required length. + *plaintext = malloc(ciphertext_len); + + cctx->decryption_counter_prev = cctx->decryption_counter; + + for (plain_block = *plaintext, cipher_block = ciphertext; cipher_block < ciphertext + ciphertext_len; ) + { + memcpy(&block_len, cipher_block, sizeof(block_len)); // TODO BE or LE? + if (cipher_block + block_len + sizeof(block_len) + AUTHTAG_LENGTH > ciphertext + ciphertext_len) + { + // The remaining ciphertext doesn't contain an entire block, so stop + break; + } + + memcpy(tag, cipher_block + sizeof(block_len) + block_len, sizeof(tag)); + memcpy(nonce + 4, &(cctx->decryption_counter), sizeof(cctx->decryption_counter));// TODO BE or LE? + + ret = decrypt_chacha(plain_block, cipher_block + sizeof(block_len), block_len, cctx->decryption_key, sizeof(cctx->decryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); + if (ret < 0) + { + cctx->errmsg = "Decryption with chacha poly1305 failed"; + cctx->decryption_counter = cctx->decryption_counter_prev; + free(*plaintext); + return -1; + } + + plain_block += block_len; + cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; + cctx->decryption_counter++; + } + + *plaintext_len = plain_block - *plaintext; + +#ifdef DEBUG_PAIR + hexdump("Decrypted:\n", *plaintext, *plaintext_len); +#endif + + return cipher_block - ciphertext; +} + +static int +state_get(const char **errmsg, const uint8_t *data, size_t data_len) +{ + pair_tlv_values_t *message; + pair_tlv_t *state; + int ret; + + if (!data || data_len == 0) + { + return 0; // state 0 = no incoming data yet -> first request + } + + message = message_process(data, data_len, errmsg); + if (!message) + { + goto error; + } + + state = pair_tlv_get_value(message, TLVType_State); + if (!state || state->size != 1) + { + *errmsg = "Could not get message state"; + goto error; + } + + ret = state->value[0]; + + pair_tlv_free(message); + return ret; + + error: + pair_tlv_free(message); + return -1; +} + +static void +public_key_get(uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id) +{ + uint8_t private_key[crypto_sign_SECRETKEYBYTES]; + server_keypair(server_public_key, private_key, device_id); +} + +const struct pair_definition pair_client_homekit_normal = +{ + .pair_setup_new = client_setup_new, + .pair_setup_free = client_setup_free, + .pair_setup_result = client_setup_result, + + .pair_setup_request1 = client_setup_request1, + .pair_setup_request2 = client_setup_request2, + .pair_setup_request3 = client_setup_request3, + + .pair_setup_response1 = client_setup_response1, + .pair_setup_response2 = client_setup_response2, + .pair_setup_response3 = client_setup_response3, + + .pair_verify_new = client_verify_new, + + .pair_verify_request1 = client_verify_request1, + .pair_verify_request2 = client_verify_request2, + + .pair_verify_response1 = client_verify_response1, + .pair_verify_response2 = client_verify_response2, + + .pair_cipher_new = cipher_new, + .pair_cipher_free = cipher_free, + + .pair_encrypt = encrypt, + .pair_decrypt = decrypt, + + .pair_state_get = state_get, +}; + +const struct pair_definition pair_client_homekit_transient = +{ + .pair_setup_new = client_setup_new, + .pair_setup_free = client_setup_free, + + .pair_setup_request1 = client_setup_request1, + .pair_setup_request2 = client_setup_request2, + .pair_setup_request3 = client_setup_request3, + + .pair_setup_response1 = client_setup_response1, + .pair_setup_response2 = client_setup_response2, + .pair_setup_response3 = client_setup_response3, + + .pair_verify_new = client_verify_new, + + .pair_verify_request1 = client_verify_request1, + .pair_verify_request2 = client_verify_request2, + + .pair_verify_response1 = client_verify_response1, + .pair_verify_response2 = client_verify_response2, + + .pair_cipher_new = cipher_new, + .pair_cipher_free = cipher_free, + + .pair_encrypt = encrypt, + .pair_decrypt = decrypt, + + .pair_state_get = state_get, +}; + +const struct pair_definition pair_server_homekit = +{ + .pair_setup_new = server_setup_new, + .pair_setup_free = server_setup_free, + + .pair_setup_request1 = server_setup_response1, + .pair_setup_request2 = server_setup_response2, + .pair_setup_request3 = server_setup_response3, + + .pair_setup_response1 = server_setup_request1, + .pair_setup_response2 = server_setup_request2, + .pair_setup_response3 = server_setup_request3, + + .pair_verify_new = server_verify_new, + + .pair_verify_request1 = server_verify_response1, + .pair_verify_request2 = server_verify_response2, + + .pair_verify_response1 = server_verify_request1, + .pair_verify_response2 = server_verify_request2, + + .pair_add = server_add_remove, + .pair_remove = server_add_remove, + .pair_list = server_list, + + .pair_cipher_new = cipher_new, + .pair_cipher_free = cipher_free, + + .pair_encrypt = encrypt, + .pair_decrypt = decrypt, + + .pair_state_get = state_get, + .pair_public_key_get = public_key_get, +}; diff --git a/pair_ap/server-example.c b/pair_ap/server-example.c new file mode 100644 index 00000000..73fe4bda --- /dev/null +++ b/pair_ap/server-example.c @@ -0,0 +1,685 @@ +/* + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "pair.h" + +#define DEVICE_ID "FFEEDDCCBBAA9988" +#define LISTEN_PORT 7000 +#define CONTENT_TYPE_OCTET "application/octet-stream" +#define RTSP_VERSION "RTSP/1.0" +#define OPTIONS "OPTIONS *" + +struct connection_ctx +{ + struct evbuffer *pending; + struct pair_setup_context *setup_ctx; + struct pair_verify_context *verify_ctx; + struct pair_cipher_context *cipher_ctx; + + int pair_completed; +}; + +struct rtsp_msg +{ + int content_length; + char *content_type; + char *first_line; + int cseq; + + const uint8_t *body; + size_t bodylen; + + const uint8_t *data; + size_t datalen; +}; + +struct pairings +{ + char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; + uint8_t public_key[32]; + + struct pairings *next; +} *pairings; + + +static void +connection_free(struct connection_ctx *conn_ctx) +{ + if (!conn_ctx) + return; + + evbuffer_free(conn_ctx->pending); + pair_setup_free(conn_ctx->setup_ctx); + pair_cipher_free(conn_ctx->cipher_ctx); + + free(conn_ctx); +} + +static void +response_headers_add(struct evbuffer *response, int cseq, size_t content_length, const char *content_type) +{ + evbuffer_add_printf(response, "%s 200 OK\r\n", RTSP_VERSION); + evbuffer_add_printf(response, "Server: MyServer/1.0\r\n"); + if (content_length) + evbuffer_add_printf(response, "Content-Length: %zu\r\n", content_length); + if (content_type) + evbuffer_add_printf(response, "Content-Type: %s\r\n", content_type); + evbuffer_add_printf(response, "CSeq: %d\r\n", cseq); + evbuffer_add_printf(response, "\r\n"); +} + +static void +response_create_from_raw(struct evbuffer *response, uint8_t *body, size_t body_len, int cseq, const char *content_type) +{ + response_headers_add(response, cseq, body_len, content_type); + + if (body) + evbuffer_add(response, body, body_len); +} + +static int +encryption_enable(struct connection_ctx *conn_ctx, const uint8_t *shared_secret, size_t shared_secret_len) +{ + conn_ctx->cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, shared_secret, shared_secret_len); + if (!conn_ctx->cipher_ctx) + { + printf("Error setting up ciphering\n"); + return -1; + } + + return 0; +} + +static int +buffer_encrypt(struct evbuffer *output, uint8_t *in, size_t in_len, struct connection_ctx *conn_ctx) +{ + uint8_t *out; + size_t out_len; + int ret; + + ret = pair_encrypt(&out, &out_len, in, in_len, conn_ctx->cipher_ctx); + if (ret < 0) + { + printf("Error encrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx)); + return -1; + } + + evbuffer_add(output, out, out_len); + free(out); + return 0; +} + +static int +buffer_decrypt(struct evbuffer *output, struct evbuffer *input, struct connection_ctx *conn_ctx) +{ + uint8_t *in; + size_t in_len; + ssize_t bytes_decrypted; + uint8_t *plain; + size_t plain_len; + + in = evbuffer_pullup(input, -1); + in_len = evbuffer_get_length(input); + + // Note that bytes_decrypted is not necessarily equal to plain_len + bytes_decrypted = pair_decrypt(&plain, &plain_len, in, in_len, conn_ctx->cipher_ctx); + if (bytes_decrypted < 0) + { + printf("Error decrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx)); + return -1; + } + + evbuffer_add(output, plain, plain_len); + evbuffer_drain(input, bytes_decrypted); + free(plain); + return 0; +} + + +/* ---------------------------- Pairing callbacks --------------------------- */ +/* Note that none of these callbacks are required if you don't care about */ +/* securely verifying the client + don't require support for the pair-add, */ +/* pair-remove and pair-list methods. */ + +static struct pairings * +pairing_find(const char *device_id) +{ + struct pairings *pairing; + + for (pairing = pairings; pairing; pairing = pairing->next) + { + if (strcmp(device_id, pairing->device_id) == 0) + break; + } + + return pairing; +} + +static int +pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg) +{ + struct pairings *pairing; + + printf("Adding paired device %s\n", device_id); + + pairing = pairing_find(device_id); + if (pairing) + { + memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); + return 0; + } + + pairing = calloc(1, sizeof(struct pairings)); + snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id); + memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); + + pairing->next = pairings; + pairings = pairing; + + return 0; +} + +static int +pairing_remove_cb(uint8_t public_key[32], const char *device_id, void *cb_arg) +{ + struct pairings *pairing; + struct pairings *iter; + + printf("Removing paired device %s\n", device_id); + + pairing = pairing_find(device_id); + if (!pairing) + { + printf("Remove callback for unknown device\n"); + return -1; + } + + if (pairing == pairings) + pairings = pairing->next; + else + { + for (iter = pairings; iter && (iter->next != pairing); iter = iter->next) + ; /* EMPTY */ + + if (iter) + iter->next = pairing->next; + } + + free(pairing); + return 0; +} + +static void +pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg) +{ + struct pairings *pairing; + + printf("Listing paired devices\n"); + + for (pairing = pairings; pairing; pairing = pairing->next) + { + enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg); + } +} + +static int +pairing_get_cb(uint8_t public_key[32], const char *device_id, void *cb_arg) +{ + struct pairings *pairing; + + printf("Returning public key for paired device %s\n", device_id); + + pairing = pairing_find(device_id); + if (!pairing) + return -1; + + memcpy(public_key, pairing->public_key, sizeof(pairing->public_key)); + + return 0; +} + + +/* -------------------------- Pair request handlers ------------------------- */ + +static int +handle_pin_start(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + printf("Please pair with code 3939\n"); + + response_create_from_raw(output, NULL, 0, msg->cseq, NULL); + + return 0; +} + +static int +handle_pair_setup(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + uint8_t *out; + size_t out_len; + struct pair_result *result; + int ret; + + if (!conn_ctx->setup_ctx) + { + conn_ctx->setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, NULL, pairing_add_cb, NULL, DEVICE_ID); + if (!conn_ctx->setup_ctx) + { + printf("Error creating setup context\n"); + return -1; + } + } + + ret = pair_setup(&out, &out_len, conn_ctx->setup_ctx, msg->body, msg->bodylen); + if (ret < 0) + { + printf("Pair setup error: %s\n", pair_setup_errmsg(conn_ctx->setup_ctx)); + return -1; + } + + ret = pair_setup_result(NULL, &result, conn_ctx->setup_ctx); + if (ret == 0 && result->shared_secret_len > 0) // Transient pairing completed (step 2) + { + encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len); + conn_ctx->pair_completed = 1; + } + + response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET); + free(out); + + return 0; +} + +static int +handle_pair_verify(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + uint8_t *out; + size_t out_len; + struct pair_result *result; + int ret; + + if (!conn_ctx->verify_ctx) + { + conn_ctx->verify_ctx = pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, pairing_get_cb, NULL, DEVICE_ID); + if (!conn_ctx->verify_ctx) + { + printf("Error creating verify context\n"); + return -1; + } + } + + ret = pair_verify(&out, &out_len, conn_ctx->verify_ctx, msg->body, msg->bodylen); + if (ret < 0) + { + printf("Pair verify error: %s\n", pair_verify_errmsg(conn_ctx->verify_ctx)); + return -1; + } + + ret = pair_verify_result(&result, conn_ctx->verify_ctx); + if (ret == 0) + { + encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len); + conn_ctx->pair_completed = 1; + } + + response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET); + free(out); + + return 0; +} + +static int +handle_pair_add(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + uint8_t *out; + size_t out_len; + int ret; + + ret = pair_add(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_add_cb, NULL, msg->body, msg->bodylen); + if (ret < 0) + { + printf("Error adding device to list\n"); + return -1; + } + + response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET); + free(out); + + return 0; +} + +static int +handle_pair_remove(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + uint8_t *out; + size_t out_len; + int ret; + + ret = pair_remove(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_remove_cb, NULL, msg->body, msg->bodylen); + if (ret < 0) + { + printf("Error removing device from list\n"); + return -1; + } + + response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET); + free(out); + + return 0; +} + +static int +handle_pair_list(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + uint8_t *out; + size_t out_len; + int ret; + + ret = pair_list(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_list_cb, NULL, msg->body, msg->bodylen); + if (ret < 0) + { + printf("Error creating list of paired devices\n"); + return -1; + } + + response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET); + free(out); + + return 0; +} + +static int +handle_options(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + struct evbuffer *response; + uint8_t *plain; + size_t plain_len; + int ret; + + response = evbuffer_new(); + + response_create_from_raw(response, NULL, 0, msg->cseq, NULL); + + if (!conn_ctx->cipher_ctx) + { + evbuffer_add_buffer(output, response); + evbuffer_free(response); + return 0; + } + + plain = evbuffer_pullup(response, -1); + plain_len = evbuffer_get_length(response); + + ret = buffer_encrypt(output, plain, plain_len, conn_ctx); + + evbuffer_free(response); + return ret; +} + +static int +response_send(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg) +{ + if (!msg->first_line) + return -1; + + if (strncmp(msg->first_line, PAIR_AP_POST_PIN_START, strlen(PAIR_AP_POST_PIN_START)) == 0) + return handle_pin_start(output, conn_ctx, msg); + else if (strncmp(msg->first_line, PAIR_AP_POST_SETUP, strlen(PAIR_AP_POST_SETUP)) == 0) + return handle_pair_setup(output, conn_ctx, msg); + else if (strncmp(msg->first_line, PAIR_AP_POST_VERIFY, strlen(PAIR_AP_POST_VERIFY)) == 0) + return handle_pair_verify(output, conn_ctx, msg); + else if (strncmp(msg->first_line, PAIR_AP_POST_ADD, strlen(PAIR_AP_POST_ADD)) == 0) + return handle_pair_add(output, conn_ctx, msg); + else if (strncmp(msg->first_line, PAIR_AP_POST_LIST, strlen(PAIR_AP_POST_LIST)) == 0) + return handle_pair_list(output, conn_ctx, msg); + else if (strncmp(msg->first_line, PAIR_AP_POST_REMOVE, strlen(PAIR_AP_POST_REMOVE)) == 0) + return handle_pair_remove(output, conn_ctx, msg); + else if (strncmp(msg->first_line, OPTIONS, strlen(OPTIONS)) == 0) + return handle_options(output, conn_ctx, msg); + + printf("Unknown method: %s\n", msg->first_line); + return -1; +} + + +/* --------------------- A basic RTSP server implementation ----------------- */ + +static void +rtsp_clear(struct rtsp_msg *msg) +{ + free(msg->first_line); + free(msg->content_type); +} + +// Very primitive RTSP message parser, hope you have a better one +static int +rtsp_parse(struct rtsp_msg *msg, uint8_t *in, size_t in_len) +{ + char *line; + int i; + + line = (char *)in; + for (i = 0; i < in_len; i++) + { + if (in[i] != '\n' && in[i - 1] != '\r') + continue; + + if (in[i - 2] == '\n' && in[i - 3] == '\r') + { + msg->bodylen = in_len - (i + 1); + if (msg->bodylen != msg->content_length) + { + printf("Incomplete read (have %zu, content-length %d), waiting for more data\n\n", msg->bodylen, msg->content_length); + rtsp_clear(msg); + return 1; + } + else if (msg->bodylen > 0) + msg->body = in + i + 1; + + break; + } + + in[i - 1] = '\0'; + + if (!msg->first_line) + msg->first_line = strdup(line); + + if (strncmp(line, "CSeq: ", strlen("CSeq: ")) == 0) + msg->cseq = atoi(line + strlen("CSeq: ")); + + if (strncmp(line, "Content-Length: ", strlen("Content-Length: ")) == 0) + msg->content_length = atoi(line + strlen("Content-Length: ")); + + if (strncmp(line, "Content-Type: ", strlen("Content-Type: ")) == 0 && !msg->content_type) + msg->content_type = strdup(line + strlen("Content-Type: ")); + + in[i - 1] = '\r'; + + line = (char *)in + i + 1; + } + + msg->data = in; + msg->datalen = in_len; + + return 0; +} + +static void +in_read_cb(struct bufferevent *bev, void *arg) +{ + struct connection_ctx *conn_ctx = arg; + struct evbuffer *input; + struct evbuffer *output; + uint8_t *plain; + size_t plain_len; + struct rtsp_msg msg = { 0 }; + int ret; + + input = bufferevent_get_input(bev); + output = bufferevent_get_output(bev); + + printf("\n--------------------------------------------------------------------------\n"); + + if (conn_ctx->pair_completed) + { + buffer_decrypt(conn_ctx->pending, input, conn_ctx); + } + else + { + evbuffer_add_buffer(conn_ctx->pending, input); + } + + // Pending holds all the message we have received so far, incl. what parts we + // might have received in previous callbacks + plain = evbuffer_pullup(conn_ctx->pending, -1); + plain_len = evbuffer_get_length(conn_ctx->pending); + + ret = rtsp_parse(&msg, plain, plain_len); + if (ret < 0) + { + printf("Could not parse RTSP message\n"); + goto error; + } + else if (ret == 1) + return; // Message incomplete, wait for more data + + ret = response_send(output, conn_ctx, &msg); + if (ret < 0) + { + goto error; + } + + error: + rtsp_clear(&msg); + evbuffer_drain(conn_ctx->pending, evbuffer_get_length(conn_ctx->pending)); + return; +} + +static void +in_event_cb(struct bufferevent *bev, short events, void *arg) +{ + struct connection_ctx *conn_ctx = arg; + + if (events & BEV_EVENT_ERROR) + printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR())); + + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) + bufferevent_free(bev); + + connection_free(conn_ctx); +} + + +/*------------------------- General server stuff ----------------------------*/ + +static void +in_accept_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *address, int socklen, void *ctx) +{ + struct event_base *base = evconnlistener_get_base(listener); + struct bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE); + struct connection_ctx *conn_ctx; + + conn_ctx = calloc(1, sizeof(struct connection_ctx)); + conn_ctx->pending = evbuffer_new(); + + bufferevent_setcb(bev, in_read_cb, NULL, in_event_cb, conn_ctx); + bufferevent_enable(bev, EV_READ | EV_WRITE); + + printf("New connection accepted\n"); +} + +static void +in_error_cb(struct evconnlistener *listener, void *ctx) +{ + int err = EVUTIL_SOCKET_ERROR(); + printf("Error occured %d (%s) on the listener\n", err, evutil_socket_error_to_string(err)); +} + +static struct evconnlistener * +listen_add(struct event_base *evbase, evconnlistener_cb req_cb, evconnlistener_errorcb err_cb, unsigned short port) +{ + struct evconnlistener *listener; + struct addrinfo hints = { 0 }; + struct addrinfo *servinfo; + char strport[8]; + int ret; + + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + + snprintf(strport, sizeof(strport), "%hu", port); + ret = getaddrinfo(NULL, strport, &hints, &servinfo); + if (ret < 0) + { + printf("getaddrinf() failed: %s\n", gai_strerror(ret)); + return NULL; + } + + listener = evconnlistener_new_bind(evbase, req_cb, NULL, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, servinfo->ai_addr, servinfo->ai_addrlen); + freeaddrinfo(servinfo); + if (!listener) + { + printf("Could not create listener for port %hu\n", port); + return NULL; + } + + evconnlistener_set_error_cb(listener, err_cb); + + return listener; +} + +int +main(int argc, char * argv[]) +{ + struct event_base *evbase; + struct evconnlistener *listener; + + evbase = event_base_new(); + + listener = listen_add(evbase, in_accept_cb, in_error_cb, LISTEN_PORT); + if (!listener) + return -1; + + printf("Listening for pairing requests on port %d\n", LISTEN_PORT); + + event_base_dispatch(evbase); + + evconnlistener_free(listener); + event_base_free(evbase); + + return 0; +}