mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 07:05:57 -05:00
parent
31ab97595e
commit
3cbb95976a
@ -650,238 +650,11 @@ chacha_encrypt(uint8_t *cipher, uint8_t *plain, size_t plain_len, const void *ad
|
||||
|
||||
/* --------------------- Helpers for sending RTSP requests ------------------ */
|
||||
|
||||
// TODO Not sure if the below is still valid for AirPlay 2
|
||||
static int
|
||||
request_header_auth_add(struct evrtsp_request *req, struct airplay_session *rs, const char *method, const char *uri)
|
||||
{
|
||||
char ha1[33];
|
||||
char ha2[33];
|
||||
char ebuf[64];
|
||||
char auth[256];
|
||||
const char *hash_fmt;
|
||||
const char *username;
|
||||
uint8_t *hash_bytes;
|
||||
size_t hashlen;
|
||||
gcry_md_hd_t hd;
|
||||
gpg_error_t gc_err;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
rs->req_has_auth = 0;
|
||||
|
||||
if (!rs->nonce)
|
||||
return 0;
|
||||
|
||||
if (!rs->password)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Authentication required but no password found for device '%s'\n", rs->devname);
|
||||
|
||||
return -2;
|
||||
}
|
||||
|
||||
hash_fmt = "%02x";
|
||||
username = ""; /* No username */
|
||||
|
||||
gc_err = gcry_md_open(&hd, GCRY_MD_MD5, 0);
|
||||
if (gc_err != GPG_ERR_NO_ERROR)
|
||||
{
|
||||
gpg_strerror_r(gc_err, ebuf, sizeof(ebuf));
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not open MD5: %s\n", ebuf);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(ha1, 0, sizeof(ha1));
|
||||
memset(ha2, 0, sizeof(ha2));
|
||||
hashlen = gcry_md_get_algo_dlen(GCRY_MD_MD5);
|
||||
|
||||
/* HA 1 */
|
||||
|
||||
gcry_md_write(hd, username, strlen(username));
|
||||
gcry_md_write(hd, ":", 1);
|
||||
gcry_md_write(hd, rs->realm, strlen(rs->realm));
|
||||
gcry_md_write(hd, ":", 1);
|
||||
gcry_md_write(hd, rs->password, strlen(rs->password));
|
||||
|
||||
hash_bytes = gcry_md_read(hd, GCRY_MD_MD5);
|
||||
if (!hash_bytes)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not read MD5 hash\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < hashlen; i++)
|
||||
sprintf(ha1 + (2 * i), hash_fmt, hash_bytes[i]);
|
||||
|
||||
/* RESET */
|
||||
gcry_md_reset(hd);
|
||||
|
||||
/* HA 2 */
|
||||
gcry_md_write(hd, method, strlen(method));
|
||||
gcry_md_write(hd, ":", 1);
|
||||
gcry_md_write(hd, uri, strlen(uri));
|
||||
|
||||
hash_bytes = gcry_md_read(hd, GCRY_MD_MD5);
|
||||
if (!hash_bytes)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not read MD5 hash\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < hashlen; i++)
|
||||
sprintf(ha2 + (2 * i), hash_fmt, hash_bytes[i]);
|
||||
|
||||
/* RESET */
|
||||
gcry_md_reset(hd);
|
||||
|
||||
/* Final value */
|
||||
gcry_md_write(hd, ha1, 32);
|
||||
gcry_md_write(hd, ":", 1);
|
||||
gcry_md_write(hd, rs->nonce, strlen(rs->nonce));
|
||||
gcry_md_write(hd, ":", 1);
|
||||
gcry_md_write(hd, ha2, 32);
|
||||
|
||||
hash_bytes = gcry_md_read(hd, GCRY_MD_MD5);
|
||||
if (!hash_bytes)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not read MD5 hash\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < hashlen; i++)
|
||||
sprintf(ha1 + (2 * i), hash_fmt, hash_bytes[i]);
|
||||
|
||||
gcry_md_close(hd);
|
||||
|
||||
/* Build header */
|
||||
ret = snprintf(auth, sizeof(auth), "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
|
||||
username, rs->realm, rs->nonce, uri, ha1);
|
||||
if ((ret < 0) || (ret >= sizeof(auth)))
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Authorization value header exceeds buffer size\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
evrtsp_add_header(req->output_headers, "Authorization", auth);
|
||||
|
||||
DPRINTF(E_DBG, L_AIRPLAY, "Authorization header: %s\n", auth);
|
||||
|
||||
rs->req_has_auth = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
static int
|
||||
response_header_auth_parse(struct airplay_session *rs, struct evrtsp_request *req)
|
||||
{
|
||||
const char *param;
|
||||
char *auth;
|
||||
char *token;
|
||||
char *ptr;
|
||||
|
||||
if (rs->realm)
|
||||
{
|
||||
free(rs->realm);
|
||||
rs->realm = NULL;
|
||||
}
|
||||
|
||||
if (rs->nonce)
|
||||
{
|
||||
free(rs->nonce);
|
||||
rs->nonce = NULL;
|
||||
}
|
||||
|
||||
param = evrtsp_find_header(req->input_headers, "WWW-Authenticate");
|
||||
if (!param)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "WWW-Authenticate header not found\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_AIRPLAY, "WWW-Authenticate: %s\n", param);
|
||||
|
||||
if (strncmp(param, "Digest ", strlen("Digest ")) != 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Unsupported authentication method: %s\n", param);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auth = strdup(param);
|
||||
if (!auth)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for WWW-Authenticate header copy\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
token = strchr(auth, ' ');
|
||||
token++;
|
||||
|
||||
token = strtok_r(token, " =", &ptr);
|
||||
while (token)
|
||||
{
|
||||
if (strcmp(token, "realm") == 0)
|
||||
{
|
||||
token = strtok_r(NULL, "=\"", &ptr);
|
||||
if (!token)
|
||||
break;
|
||||
|
||||
rs->realm = strdup(token);
|
||||
}
|
||||
else if (strcmp(token, "nonce") == 0)
|
||||
{
|
||||
token = strtok_r(NULL, "=\"", &ptr);
|
||||
if (!token)
|
||||
break;
|
||||
|
||||
rs->nonce = strdup(token);
|
||||
}
|
||||
|
||||
token = strtok_r(NULL, " =", &ptr);
|
||||
}
|
||||
|
||||
free(auth);
|
||||
|
||||
if (!rs->realm || !rs->nonce)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not find realm/nonce in WWW-Authenticate header\n");
|
||||
|
||||
if (rs->realm)
|
||||
{
|
||||
free(rs->realm);
|
||||
rs->realm = NULL;
|
||||
}
|
||||
|
||||
if (rs->nonce)
|
||||
{
|
||||
free(rs->nonce);
|
||||
rs->nonce = NULL;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_AIRPLAY, "Found realm: [%s], nonce: [%s]\n", rs->realm, rs->nonce);
|
||||
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
static int
|
||||
request_headers_add(struct evrtsp_request *req, struct airplay_session *rs, enum evrtsp_cmd_type req_method)
|
||||
{
|
||||
char buf[64];
|
||||
const char *method;
|
||||
const char *url;
|
||||
const char *user_agent;
|
||||
int ret;
|
||||
|
||||
method = evrtsp_method(req_method);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", rs->cseq);
|
||||
evrtsp_add_header(req->output_headers, "CSeq", buf);
|
||||
@ -891,20 +664,6 @@ request_headers_add(struct evrtsp_request *req, struct airplay_session *rs, enum
|
||||
user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent");
|
||||
evrtsp_add_header(req->output_headers, "User-Agent", user_agent);
|
||||
|
||||
/* Add Authorization header */
|
||||
url = (req_method == EVRTSP_REQ_OPTIONS) ? "*" : rs->session_url;
|
||||
|
||||
ret = request_header_auth_add(req, rs, method, url);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "Could not add Authorization header\n");
|
||||
|
||||
if (ret == -2)
|
||||
rs->state = AIRPLAY_STATE_AUTH;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%" PRIX64, libhash);
|
||||
evrtsp_add_header(req->output_headers, "Client-Instance", buf);
|
||||
evrtsp_add_header(req->output_headers, "DACP-ID", buf);
|
||||
@ -2635,7 +2394,7 @@ Good to know (source Apple's MFi Accessory Interface Specification):
|
||||
- New keys should be generated for each authentication attempt, but we don't
|
||||
do that because we don't really use this + it adds a libsodium dependency
|
||||
|
||||
Since we don't do auth or encryption, we currently just ignore the reponse.
|
||||
Since we don't do auth nor encryption, we currently just ignore the reponse.
|
||||
*/
|
||||
|
||||
#if AIRPLAY_USE_AUTH_SETUP
|
||||
@ -2716,9 +2475,12 @@ payload_make_pair_generic(int step, struct evrtsp_request *req, struct airplay_s
|
||||
static int
|
||||
payload_make_pair_setup1(struct evrtsp_request *req, struct airplay_session *rs, void *arg)
|
||||
{
|
||||
char *pin = arg;
|
||||
const char *pin = arg;
|
||||
char device_id_hex[16 + 1];
|
||||
|
||||
if (!pin && rs->password)
|
||||
pin = rs->password; // For password based authentication
|
||||
|
||||
if (pin)
|
||||
rs->pair_type = PAIR_CLIENT_HOMEKIT_NORMAL;
|
||||
|
||||
@ -3045,7 +2807,7 @@ response_handler_info_generic(struct evrtsp_request *req, struct airplay_session
|
||||
rs->devname, rs->statusflags, (bool)(rs->statusflags & AIRPLAY_FLAG_AUDIO_CABLE_ATTACHED), (bool)(rs->statusflags & AIRPLAY_FLAG_ONE_TIME_PAIRING_REQUIRED),
|
||||
(bool)(rs->statusflags & AIRPLAY_FLAG_PASSWORD_REQUIRED), (bool)(rs->statusflags & AIRPLAY_FLAG_PIN_REQUIRED));
|
||||
|
||||
// Evaluate what next sequence based on response
|
||||
// Identify next sequence based on response
|
||||
if (rs->statusflags & AIRPLAY_FLAG_ONE_TIME_PAIRING_REQUIRED)
|
||||
{
|
||||
rs->pair_type = PAIR_CLIENT_HOMEKIT_NORMAL;
|
||||
@ -3072,9 +2834,21 @@ response_handler_info_generic(struct evrtsp_request *req, struct airplay_session
|
||||
}
|
||||
else if (rs->statusflags & AIRPLAY_FLAG_PASSWORD_REQUIRED)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "'%s' requires password authentication, but that is currently unsupported for AirPlay 2\n", rs->devname);
|
||||
rs->state = AIRPLAY_STATE_AUTH;
|
||||
return AIRPLAY_SEQ_ABORT;
|
||||
rs->pair_type = PAIR_CLIENT_HOMEKIT_NORMAL;
|
||||
|
||||
if (!rs->password)
|
||||
{
|
||||
DPRINTF(E_LOG, L_AIRPLAY, "'%s' requires password authentication, but none given in config\n", rs->devname);
|
||||
return AIRPLAY_SEQ_ABORT;
|
||||
}
|
||||
else if (!device->auth_key)
|
||||
{
|
||||
rs->state = AIRPLAY_STATE_AUTH;
|
||||
return AIRPLAY_SEQ_PAIR_SETUP;
|
||||
}
|
||||
|
||||
rs->state = AIRPLAY_STATE_INFO;
|
||||
return AIRPLAY_SEQ_PAIR_VERIFY;
|
||||
}
|
||||
|
||||
rs->pair_type = PAIR_CLIENT_HOMEKIT_TRANSIENT;
|
||||
@ -3088,11 +2862,10 @@ response_handler_info_probe(struct evrtsp_request *req, struct airplay_session *
|
||||
enum airplay_seq_type seq_type;
|
||||
|
||||
seq_type = response_handler_info_generic(req, rs);
|
||||
if (seq_type == AIRPLAY_SEQ_ABORT || seq_type == AIRPLAY_SEQ_PIN_START)
|
||||
return seq_type;
|
||||
if (seq_type == AIRPLAY_SEQ_PAIR_TRANSIENT || seq_type == AIRPLAY_SEQ_PAIR_VERIFY)
|
||||
seq_type = AIRPLAY_SEQ_CONTINUE; // When probing we don't proceed to PAIR_TRANSIENT/VERIFY
|
||||
|
||||
// When probing we don't want to continue with PAIR_VERIFY or PAIR_TRANSIENT
|
||||
return AIRPLAY_SEQ_CONTINUE;
|
||||
return seq_type;
|
||||
}
|
||||
|
||||
static enum airplay_seq_type
|
||||
@ -3101,11 +2874,8 @@ response_handler_info_start(struct evrtsp_request *req, struct airplay_session *
|
||||
enum airplay_seq_type seq_type;
|
||||
|
||||
seq_type = response_handler_info_generic(req, rs);
|
||||
if (seq_type == AIRPLAY_SEQ_ABORT || seq_type == AIRPLAY_SEQ_PIN_START)
|
||||
return seq_type;
|
||||
|
||||
// Pair and then run SEQ_START_PLAYBACK which sets up the playback
|
||||
rs->next_seq = AIRPLAY_SEQ_START_PLAYBACK;
|
||||
if (seq_type != AIRPLAY_SEQ_ABORT && seq_type != AIRPLAY_SEQ_PIN_START)
|
||||
rs->next_seq = AIRPLAY_SEQ_START_PLAYBACK; // Pair and then run SEQ_START_PLAYBACK which sets up the playback
|
||||
|
||||
return seq_type;
|
||||
}
|
||||
@ -3657,6 +3427,7 @@ airplay_device_cb(const char *name, const char *type, const char *domain, const
|
||||
cfg_opt_t *cfgopt;
|
||||
const char *p;
|
||||
const char *nickname = NULL;
|
||||
const char *password = NULL;
|
||||
const char *features;
|
||||
uint64_t id;
|
||||
int ret;
|
||||
@ -3704,12 +3475,17 @@ airplay_device_cb(const char *name, const char *type, const char *domain, const
|
||||
{
|
||||
nickname = cfg_getstr(devcfg, "nickname");
|
||||
}
|
||||
if (devcfg && cfg_getstr(devcfg, "password"))
|
||||
{
|
||||
password = cfg_getstr(devcfg, "password");
|
||||
}
|
||||
|
||||
CHECK_NULL(L_AIRPLAY, rd = calloc(1, sizeof(struct output_device)));
|
||||
CHECK_NULL(L_AIRPLAY, re = calloc(1, sizeof(struct airplay_extra)));
|
||||
|
||||
rd->id = id;
|
||||
rd->name = nickname ? strdup(nickname) : strdup(name);
|
||||
rd->password = password;
|
||||
re->mdns_name = strdup(name); // Used for identifying device when it disappears
|
||||
rd->type = OUTPUT_TYPE_AIRPLAY;
|
||||
rd->type_name = outputs_name(rd->type);
|
||||
|
@ -15,7 +15,7 @@ struct pair_client_setup_context
|
||||
{
|
||||
struct SRPUser *user;
|
||||
|
||||
uint8_t pin[4];
|
||||
char *pin;
|
||||
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
|
||||
|
||||
pair_cb add_cb;
|
||||
@ -50,7 +50,7 @@ struct pair_server_setup_context
|
||||
{
|
||||
struct SRPVerifier *verifier;
|
||||
|
||||
uint8_t pin[4];
|
||||
char *pin;
|
||||
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
|
||||
|
||||
pair_cb add_cb;
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#define PAIR_AP_VERSION_MAJOR 0
|
||||
#define PAIR_AP_VERSION_MINOR 12
|
||||
#define PAIR_AP_VERSION_MINOR 14
|
||||
|
||||
#define PAIR_AP_DEVICE_ID_LEN_MAX 64
|
||||
|
||||
|
@ -600,10 +600,12 @@ client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add
|
||||
if (!is_initialized())
|
||||
return -1;
|
||||
|
||||
if (!pin || strlen(pin) < 4)
|
||||
if (!pin)
|
||||
return -1;
|
||||
|
||||
memcpy(sctx->pin, pin, sizeof(sctx->pin));
|
||||
sctx->pin = strdup(pin);
|
||||
if (!sctx->pin)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -620,6 +622,7 @@ client_setup_free(struct pair_setup_context *handle)
|
||||
free(sctx->salt);
|
||||
free(sctx->epk);
|
||||
free(sctx->authtag);
|
||||
free(sctx->pin);
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
@ -632,7 +635,7 @@ client_setup_request1(size_t *len, struct pair_setup_context *handle)
|
||||
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);
|
||||
sctx->user = srp_user_new(HASH_SHA1, SRP_NG_2048, USERNAME, (unsigned char *)sctx->pin, strlen(sctx->pin), 0, 0);
|
||||
|
||||
dict = plist_new_dict();
|
||||
|
||||
|
@ -1173,20 +1173,17 @@ client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add
|
||||
if (!is_initialized())
|
||||
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 (!pin && handle->type == &pair_client_homekit_transient)
|
||||
pin = "3939";
|
||||
else if (!pin)
|
||||
return -1;
|
||||
|
||||
if (device_id && strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
|
||||
return -1;
|
||||
|
||||
memcpy(sctx->pin, pin, sizeof(sctx->pin));
|
||||
sctx->pin = strdup(pin);
|
||||
if (!sctx->pin)
|
||||
return -1;
|
||||
|
||||
sctx->add_cb = add_cb;
|
||||
sctx->add_cb_arg = cb_arg;
|
||||
@ -1216,6 +1213,7 @@ client_setup_free(struct pair_setup_context *handle)
|
||||
free(sctx->salt);
|
||||
free(sctx->epk);
|
||||
free(sctx->authtag);
|
||||
free(sctx->pin);
|
||||
}
|
||||
|
||||
static uint8_t *
|
||||
@ -1241,7 +1239,7 @@ client_setup_request1(size_t *len, struct pair_setup_context *handle)
|
||||
goto error;
|
||||
}
|
||||
|
||||
sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0);
|
||||
sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, strlen(sctx->pin), 0, 0);
|
||||
if (!sctx->user)
|
||||
{
|
||||
handle->errmsg = "Setup request 1: Create SRP user failed";
|
||||
@ -2004,7 +2002,9 @@ server_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add
|
||||
if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX)
|
||||
return -1;
|
||||
|
||||
memcpy(sctx->pin, pin, sizeof(sctx->pin));
|
||||
sctx->pin = strdup(pin);
|
||||
if (!sctx->pin)
|
||||
return -1;
|
||||
|
||||
sctx->add_cb = add_cb;
|
||||
sctx->add_cb_arg = cb_arg;
|
||||
@ -2029,6 +2029,7 @@ server_setup_free(struct pair_setup_context *handle)
|
||||
free(sctx->M1);
|
||||
free(sctx->v);
|
||||
free(sctx->salt);
|
||||
free(sctx->pin);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -2057,7 +2058,7 @@ server_setup_request1(struct pair_setup_context *handle, const uint8_t *data, si
|
||||
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),
|
||||
ret = srp_create_salted_verification_key(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, strlen(sctx->pin),
|
||||
&sctx->salt, &sctx->salt_len, &sctx->v, &sctx->v_len, NULL, NULL);
|
||||
if (ret < 0)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user