--- /dev/null
+# 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)
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+
+#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;
+}
--- /dev/null
+#include <stdint.h>
+#include <stdbool.h>
+#include <sodium.h>
+
+#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 <gcrypt.h>
+#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 <openssl/crypto.h>
+#include <openssl/bn.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#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
--- /dev/null
+/*
+ * TLV helpers are adapted from ESP homekit:
+ * <https://github.com/maximkulkin/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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#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
+
--- /dev/null
+#ifndef __PAIR_AP_TLV_H__
+#define __PAIR_AP_TLV_H__
+
+#include <stdint.h>
+
+#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__ */
--- /dev/null
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h> // for isprint()
+
+#include <sodium.h>
+
+#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);
+}
--- /dev/null
+#ifndef __PAIR_AP_H__
+#define __PAIR_AP_H__
+
+#include <stdint.h>
+
+#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__ */
--- /dev/null
+*.o
+*.lo
+*.a
+*.la
+.dirstamp
+.deps/
+.libs/
+
+client-example
+server-example
--- /dev/null
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
+ * 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 <event2/event.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <winsock2.h>
+#include <windows.h>
+#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_ */
--- /dev/null
+/*
+ * Copyright (c) 2000-2004 Niels Provos <provos@citi.umich.edu>
+ * 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
--- /dev/null
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright 2001 Niels Provos <provos@citi.umich.edu>
+ * 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 <event2/buffer.h>
+#include <event2/event_struct.h>
+
+#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 */
--- /dev/null
+/*
+ * Copyright (C) 2010 Julien BLACHE <jb@jblache.org>
+ * Based on evhttp from libevent 1.4.x
+ *
+ * Copyright (c) 2002-2006 Niels Provos <provos@citi.umich.edu>
+ * 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 <sys/param.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <event2/event-config.h>
+
+#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);
+}
--- /dev/null
+/*
+ *
+ * The Secure Remote Password 6a implementation is adapted from:
+ * - Tom Cocagne
+ * <https://github.com/cocagne/csrp>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <plist/plist.h>
+#include <sodium.h>
+
+#include <assert.h>
+
+#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,
+};
--- /dev/null
+/*
+ * Adaption of ap2-sender by ViktoriiaKh:
+ * <https://github.com/ViktoriiaKh/ap2-sender>
+ *
+ * To test, it is useful to try with this receiver:
+ * <https://github.com/ckdo/airplay2-receiver>
+ *
+ * The Secure Remote Password 6a implementation is adapted from:
+ * - Tom Cocagne
+ * <https://github.com/cocagne/csrp>
+ *
+ * TLV helpers are adapted from ESP homekit:
+ * <https://github.com/maximkulkin/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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <assert.h>
+
+#include <sodium.h>
+
+#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 <openssl/kdf.h>
+ 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,
+};
--- /dev/null
+/*
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <assert.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+#include <event2/listener.h>
+
+#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;
+}