diff --git a/lib/pairing.c b/lib/pairing.c index 49c2600..5e1d10a 100644 --- a/lib/pairing.c +++ b/lib/pairing.c @@ -18,6 +18,7 @@ #include #include #include +#include #include // for SHA512_DIGEST_LENGTH #include "pairing.h" @@ -27,11 +28,12 @@ #define SALT_KEY "Pair-Verify-AES-Key" #define SALT_IV "Pair-Verify-AES-IV" -typedef struct srp_user_s { - char username[SRP_USERNAME_SIZE + 1]; +typedef struct srp_s { unsigned char salt[SRP_SALT_SIZE]; unsigned char verifier[SRP_VERIFIER_SIZE]; -} srp_user_t; + unsigned char session_key[SRP_SESSION_KEY_SIZE]; + unsigned char private_key[SRP_PRIVATE_KEY_SIZE]; +} srp_t; struct pairing_s { ed25519_key_t *ed; @@ -54,10 +56,12 @@ struct pairing_session_s { x25519_key_t *ecdh_theirs; unsigned char ecdh_secret[X25519_KEY_SIZE]; + char username[SRP_USERNAME_SIZE + 1]; + unsigned char client_pk[ED25519_KEY_SIZE]; + bool pair_setup; + /* srp items */ - srp_user_t *srp_user; - unsigned char srp_session_key[SRP_SESSION_KEY_SIZE]; - unsigned char srp_private_key[SRP_PRIVATE_KEY_SIZE]; + srp_t *srp; }; static int @@ -131,6 +135,7 @@ pairing_session_init(pairing_t *pairing) session->ed_ours = ed25519_key_copy(pairing->ed); session->status = STATUS_INITIAL; + session->srp = NULL; return session; } @@ -267,6 +272,10 @@ pairing_session_destroy(pairing_session_t *session) x25519_key_destroy(session->ecdh_ours); x25519_key_destroy(session->ecdh_theirs); + if (session->srp) { + free(session->srp); + session->srp = NULL; + } free(session); } } @@ -303,18 +312,20 @@ srp_new_user(pairing_session_t *session, pairing_t *pairing, const char *device_ return -1; } - if (session->srp_user) { - free (session->srp_user); + strncpy(session->username, device_id, SRP_USERNAME_SIZE); + + if (session->srp) { + free (session->srp); + session->srp = NULL; } - session->srp_user = (srp_user_t *) malloc(sizeof(srp_user_t)); - if (!session->srp_user) { + session->srp = (srp_t *) calloc(1, sizeof(srp_t)); + if (!session->srp) { return -2; } - memset(session->srp_user, 0, sizeof(srp_user_t)); - strncpy(session->srp_user->username, device_id, SRP_USERNAME_SIZE); - get_random_bytes(session->srp_private_key, SRP_PRIVATE_KEY_SIZE); + + get_random_bytes(session->srp->private_key, SRP_PRIVATE_KEY_SIZE); - const unsigned char *srp_b = session->srp_private_key; + const unsigned char *srp_b = session->srp->private_key; unsigned char * srp_B; unsigned char * srp_s; unsigned char * srp_v; @@ -331,12 +342,10 @@ srp_new_user(pairing_session_t *session, pairing_t *pairing, const char *device_ return -3; } + memcpy(session->srp->salt, srp_s, SRP_SALT_SIZE); + memcpy(session->srp->verifier, srp_v, SRP_VERIFIER_SIZE); - - memcpy(session->srp_user->salt, srp_s, SRP_SALT_SIZE); - memcpy(session->srp_user->verifier, srp_v, SRP_VERIFIER_SIZE); - - *salt = (char *) session->srp_user->salt; + *salt = (char *) session->srp->salt; *len_salt = len_s; srp_create_server_ephemeral_key(SRP_SHA, SRP_NG, @@ -356,16 +365,16 @@ srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigne int len_A, unsigned char *proof, int client_proof_len, int proof_len) { int authenticated = 0; const unsigned char *B = NULL; - const unsigned char *b = session->srp_private_key; + const unsigned char *b = session->srp->private_key; int len_b = SRP_PRIVATE_KEY_SIZE; int len_B = 0; int len_K = 0; const unsigned char *session_key = NULL; const unsigned char *M2 = NULL; - struct SRPVerifier *verifier = srp_verifier_new(SRP_SHA, SRP_NG, (const char *) session->srp_user->username, - (const unsigned char *) session->srp_user->salt, SRP_SALT_SIZE, - (const unsigned char *) session->srp_user->verifier, SRP_VERIFIER_SIZE, + struct SRPVerifier *verifier = srp_verifier_new(SRP_SHA, SRP_NG, (const char *) session->username, + (const unsigned char *) session->srp->salt, SRP_SALT_SIZE, + (const unsigned char *) session->srp->verifier, SRP_VERIFIER_SIZE, A, len_A, b, len_b, &B, &len_B, NULL, NULL, 1); @@ -375,15 +384,15 @@ srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigne if (authenticated == 0) { /* HTTP 470 should be sent to client if not verified.*/ srp_verifier_delete(verifier); - free (session->srp_user); - session->srp_user = NULL; + free (session->srp); + session->srp = NULL; return -1; } session_key = srp_verifier_get_session_key(verifier, &len_K); if (len_K != SRP_SESSION_KEY_SIZE) { return -2; } - memcpy(session->srp_session_key, session_key, len_K); + memcpy(session->srp->session_key, session_key, len_K); memcpy(proof, M2, proof_len); srp_verifier_delete(verifier); return 0; @@ -395,13 +404,12 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsigned char hash[SHA512_DIGEST_LENGTH]; unsigned char pk[ED25519_KEY_SIZE]; int pk_len_client, epk_len; - /* decrypt client epk to get client pk, authenticate with auth_tag*/ const char *salt = "Pair-Setup-AES-Key"; sha_ctx_t *ctx = sha_init(); sha_update(ctx, (const unsigned char *) salt, strlen(salt)); - sha_update(ctx, session->srp_session_key, SRP_SESSION_KEY_SIZE); + sha_update(ctx, session->srp->session_key, SRP_SESSION_KEY_SIZE); sha_final(ctx, hash, NULL); sha_destroy(ctx); memcpy(aesKey, hash, 16); @@ -409,12 +417,16 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, salt = "Pair-Setup-AES-IV"; ctx = sha_init(); sha_update(ctx, (const unsigned char *) salt, strlen(salt)); - sha_update(ctx, session->srp_session_key, SRP_SESSION_KEY_SIZE); + sha_update(ctx, session->srp->session_key, SRP_SESSION_KEY_SIZE); sha_final(ctx, hash, NULL); sha_destroy(ctx); memcpy(aesIV, hash, 16); aesIV[15]++; + /* SRP6a data is no longer needed */ + free(session->srp); + session->srp = NULL; + /* decrypt client epk to authenticate client using auth_tag */ pk_len_client = gcm_decrypt(epk, ED25519_KEY_SIZE, pk, aesKey, aesIV, auth_tag); if (pk_len_client <= 0) { @@ -422,7 +434,11 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, return pk_len_client; } - /* encrypt server epk so client can authenticate server using auth_tag */ + /* success, from server viewpoint */ + memcpy(session->client_pk, pk, ED25519_KEY_SIZE); + session->pair_setup = true; + + /* encrypt server epk so client can also authenticate server using auth_tag */ pairing_get_public_key(pairing, pk); /* encryption needs this previously undocumented additional "nonce" */ @@ -430,3 +446,9 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, epk_len = gcm_encrypt(pk, ED25519_KEY_SIZE, epk, aesKey, aesIV, auth_tag); return epk_len; } + +void access_client_session_data(pairing_session_t *session, char **username, unsigned char **client_pk, bool *setup) { + *username = session->username; + *client_pk = session->client_pk; + setup = &(session->pair_setup); +} diff --git a/lib/pairing.h b/lib/pairing.h index 00b116b..03160a1 100644 --- a/lib/pairing.h +++ b/lib/pairing.h @@ -60,4 +60,5 @@ int srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const uns int len_A, unsigned char *proof, int client_proof_len, int proof_len); int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsigned char *epk, unsigned char *auth_tag); +void access_client_session_data(pairing_session_t *session, char **username, unsigned char **client_pk, bool *setup); #endif diff --git a/lib/raop.c b/lib/raop.c index d656b8a..c863109 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -68,7 +68,8 @@ struct raop_s { /* for temporary storage of pin during pair-pin start */ unsigned short pin; - + bool use_pin; + /* public key as string */ char pk_str[2*ED25519_KEY_SIZE + 1]; }; @@ -481,7 +482,8 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile) { /* initialise stored pin */ raop->pin = 0; - + raop->use_pin = false; + /* initialize switch for display of client's streaming data records */ raop->clientFPSdata = 0; @@ -552,6 +554,7 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) { if (raop->audio_delay_micros != value) retval = 1; } else if (strcmp(plist_item, "pin") == 0) { raop->pin = value; + raop->use_pin = true; } else { retval = -1; } diff --git a/lib/raop.h b/lib/raop.h index e093476..19eb9c3 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -60,6 +60,8 @@ struct raop_callbacks_s { void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height); void (*report_client_request) (void *cls, char *deviceid, char *model, char *name, bool *admit); void (*display_pin) (void *cls, char * pin); + void (*register_client) (void *cls, const char *device_id, const char *pk_str); + bool (*check_register) (void *cls, const char *pk_str); }; typedef struct raop_callbacks_s raop_callbacks_t; raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote, int remote_addr_len, diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 007d82c..679cb72 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -341,8 +341,17 @@ raop_handler_pairsetup_pin(raop_conn_t *conn, logger_log(conn->raop->logger, LOGGER_ERR, "pair-pin-setup (step 3): client authentication failed\n"); goto authentication_failed; } else { - logger_log(conn->raop->logger, LOGGER_INFO, "pair-pin-setup success\n"); - } + bool client_pair_setup; + char *client_device_id; + unsigned char *client_pk; + access_client_session_data(conn->session, &client_device_id, &client_pk, &client_pair_setup); + char * client_pk_str = utils_pk_to_string(client_pk, ED25519_KEY_SIZE); + if (conn->raop->callbacks.register_client) { + conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk_str); + } + free (client_pk_str); + logger_log(conn->raop->logger, LOGGER_DEBUG, "pair-pin-setup success\n"); + } pairing_session_set_setup_status(conn->session); plist_t res_root_node = plist_new_dict(); plist_t res_epk_node = plist_new_data((const char *) epk, 32); @@ -397,9 +406,15 @@ raop_handler_pairverify(raop_conn_t *conn, http_request_t *request, http_response_t *response, char **response_data, int *response_datalen) { - //if (pairing_session_check_handshake_status(conn->session)) { - // return; - // } + bool register_check = false; + if (pairing_session_check_handshake_status(conn->session)) { + if (conn->raop->use_pin) { + pairing_session_set_setup_status(conn->session); + register_check = true; + } else { + return; + } + } unsigned char public_key[X25519_KEY_SIZE]; unsigned char signature[PAIRING_SIG_SIZE]; const unsigned char *data; @@ -412,7 +427,7 @@ raop_handler_pairverify(raop_conn_t *conn, } switch (data[0]) { case 1: - if (datalen != 4 + X25519_KEY_SIZE + X25519_KEY_SIZE) { + if (datalen != 4 + X25519_KEY_SIZE + ED25519_KEY_SIZE) { logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-verify data"); return; } @@ -426,6 +441,17 @@ raop_handler_pairverify(raop_conn_t *conn, if (pairing_session_get_signature(conn->session, signature)) { logger_log(conn->raop->logger, LOGGER_ERR, "Error getting ED25519 signature"); } + if (register_check) { + char *pk_str = utils_pk_to_string((const unsigned char *)(data + 4 + X25519_KEY_SIZE), ED25519_KEY_SIZE); + bool registered_client = true; + if (conn->raop->callbacks.check_register) { + registered_client = conn->raop->callbacks.check_register(conn->raop->callbacks.cls, pk_str); + } + free (pk_str); + if (!registered_client) { + return; + } + } *response_data = malloc(sizeof(public_key) + sizeof(signature)); if (*response_data) { http_response_add_header(response, "Content-Type", "application/octet-stream"); diff --git a/uxplay.cpp b/uxplay.cpp index a8e4e96..09a76ee 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1569,6 +1569,17 @@ extern "C" void audio_set_metadata(void *cls, const void *buffer, int buflen) { } } +extern "C" void register_client(void *cls, const char *device_id, const char *client_pk_str) { + /* pair-setup-pin client registration by the server is not implemented here, do nothing*/ + LOGD("registered new client: DeviceID = %s\nPK = \"%s\"", device_id, client_pk_str); +} + +extern "C" bool check_register(void *cls, const char *client_pk_str) { + /* pair-setup-pin client registration by the server is not implemented here, return "true"*/ + LOGD("register check returning client:\nPK = \"%s\"", client_pk_str); + return true; +} + extern "C" void log_callback (void *cls, int level, const char *msg) { switch (level) { case LOGGER_DEBUG: { @@ -1612,6 +1623,8 @@ int start_raop_server (unsigned short display[5], unsigned short tcp[3], unsigne raop_cbs.audio_set_coverart = audio_set_coverart; raop_cbs.report_client_request = report_client_request; raop_cbs.display_pin = display_pin; + raop_cbs.register_client = register_client; + raop_cbs.check_register = check_register; /* set max number of connections = 2 to protect against capture by new client */ raop = raop_init(max_connections, &raop_cbs, keyfile.c_str()); @@ -1632,7 +1645,7 @@ int start_raop_server (unsigned short display[5], unsigned short tcp[3], unsigne if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1); raop_set_plist(raop, "max_ntp_timeouts", max_ntp_timeouts); if (audiodelay >= 0) raop_set_plist(raop, "audio_delay_micros", audiodelay); - if (pin) raop_set_plist(raop, "pin", (int) pin); + if (require_password) raop_set_plist(raop, "pin", (int) pin); /* network port selection (ports listed as "0" will be dynamically assigned) */ raop_set_tcp_ports(raop, tcp);