Merge pull request #254 from FDH2/pin

pin-pairing improvements
This commit is contained in:
fduncanh
2023-12-27 03:48:28 +08:00
committed by GitHub
12 changed files with 342 additions and 95 deletions

View File

@@ -1,6 +1,6 @@
<h1
id="uxplay-1.67-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
id="uxplay-1.68-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
(now also runs on Windows).</h1>
<h3
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found.">Now
@@ -9,9 +9,13 @@ href="https://github.com/FDH2/UxPlay">https://github.com/FDH2/UxPlay</a>
(where ALL user issues should be posted, and latest versions can be
found).</h3>
<ul>
<li><em><strong>NEW in v1.67</strong>: support for one-time Apple-style
“pin” code client authentication (“client-server pairing”) when the
option “-pin” is used.</em></li>
<li><em><strong>NEW in v1.68</strong>: improved support for one-time
Apple-style “pin” codes introduced in 1.67: a register of pin-registered
clients is now optionally maintained to check returning clients; a
simpler method for generating a persistent public key (based on the MAC
address, which now can be set in the UxPlay startup file) is now the
default. (The pem-file method introduced in 1.67 is still available with
the -key” option.)</em></li>
</ul>
<h2 id="highlights">Highlights:</h2>
<ul>
@@ -461,12 +465,17 @@ clients to “pair” with the UxPlay server the first time they connect to
it, by entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is a
corporately-owned and -managed device with MDM Mobile Device
Management.) Pairing occurs just once, is curently only recorded in the
client, and persists unless the UxPlay public key (stored in
$HOME/.uxplay.pem, or elsewhere if option
<code>-key &lt;filename&gt;</code> is used) is moved or deleted, after
which a new key is generated. (Non-Apple clients might not implement the
persistence feature.)</p></li>
Management.) Pairing occurs just once, is currently only recorded in the
client, and persists unless the UxPlay public key is changed. By default
(since v1.68) the public key is now generated using the “Device ID”,
which is either the servers hardware MAC address, or can be set with
the -m option (most conveniently using the startup option file).
(Storage of a more securely-generated persistent key as an OpenSSL “pem”
file is still available with the -key option). For use of uxplay in a
more public environment, a list of previously-registered clients can
(since v1.68) be optionally-maintained using the -reg option: without
this option, returning clients claiming to be registered are just
trusted and not checked.</p></li>
<li><p>By default, UxPlay is locked to its current client until that
client drops the connection; since UxPlay-1.58, the option
<code>-nohold</code> modifies this behavior so that when a new client
@@ -895,6 +904,14 @@ UxPlay startups. As long as this file is not deleted or moved, a client
will not have to re-authenticate after an initial authentication.
<em>(Add a “pin” entry in the UxPlay startup file if you wish the UxPlay
server to use this protocol).</em></p>
<p><strong>-reg [<em>filename</em>]</strong>: (since v1.68). This option
maintains a list of previously-pin-registered clients in
$HOME/.uxplay.register (or optionally, in <em>filename</em>). Without
this option, returning clients claiming to be already pin-registered are
trusted and not checked. (This option may be useful if UxPlay is used in
a more public environment, to record client details; the register is
text, one line per client, with clients public key (base-64 format),
Device ID, and Device name.)</p>
<p><strong>-vsync [x]</strong> (In Mirror mode:) this option
(<strong>now the default</strong>) uses timestamps to synchronize audio
with video on the server, with an optional audio delay in (decimal)
@@ -1107,13 +1124,27 @@ card, (more specifically, the MAC address used by the first active
network interface detected) a random MAC address will be used even if
option <strong>-m</strong> was not specified. (Note that a random MAC
address will be different each time UxPlay is started).</p>
<p><strong>-key [<em>filename</em>]</strong>: By default, the storage of
the Server private key is in the file $HOME/.uxplay.pem. Use the “-key
<em>filename</em>” option to change this location. This option should be
set in the UxPlay startup file as a line “<code>key filename</code>” (no
initial “-”), where <code>filename</code> is a full path. The filename
may be enclosed in quotes (<code>"...."</code>), (and must be, if the
filename has any blank spaces).</p>
<p><strong>-key [<em>filename</em>]</strong>: This (more secure) option
for generating and storing a persistant public key (needed for the -pin
option) has been replaced by default with a (less secure) method which
generates a key from the servers “device ID” (MAC address, which can be
changed with the -m option, conveniently as a startup file option). When
the -key option is used, a securely generated keypair is generated and
stored in <code>$HOME/.uxplay.pem</code>, if that file does not exist,
or read from it, if it exists. (Optionally, the key can be stored in
<em>filename</em>.) This method is more secure than the new default
method, (because the Device ID is broadcast in the DNS_SD announcement)
but still leaves the private key exposed to anyone who can access the
pem file. Because the default (but “less-secure”) “Device ID” method is
simpler, and security of client access to uxplay is unlikely to be an
important issue, the -key option is no longer recommended.</p>
<p>By default, the storage of the Server private key is in the file
$HOME/.uxplay.pem. Use the “-key <em>filename</em>” option to change
this location. This option should be set in the UxPlay startup file as a
line “<code>key filename</code>” (no initial “-”), where
<code>filename</code> is a full path. The filename may be enclosed in
quotes (<code>"...."</code>), (and must be, if the filename has any
blank spaces).</p>
<p><strong>-dacp [<em>filename</em>]</strong>: Export current client
DACP-ID and Active-Remote key to file: default is $HOME/.uxplay.dacp.
(optionally can be changed to <em>filename</em>). Can be used by remote
@@ -1463,6 +1494,11 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.</p>
<h1 id="changelog">Changelog</h1>
<p>1.68 2023-12-25 Introduced a simpler (default) method for generating
a persistent public key from the server MAC address (which can now be
set with the -m option). (The previous pem-file method is still
available with -key option). New option -reg to maintain a register of
pin-authenticated clients.</p>
<p>1.67 2023-11-30 Add support for Apple-style one-time pin
authentication of clients with option “-pin”: (uses SRP6a authentication
protocol and public key persistence). Detection with error message of

View File

@@ -1,9 +1,12 @@
# UxPlay 1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).
* _**NEW in v1.67**: support for one-time Apple-style "pin" code client authentication ("client-server
pairing") when the option "-pin" is used._
* _**NEW in v1.68**: improved support for one-time Apple-style "pin" codes introduced in 1.67: a register of pin-registered clients is
now optionally maintained to check returning clients; a simpler method for generating a persistent public key (based on
the MAC address, which now can be set in the UxPlay startup file) is now the default. (The pem-file method introduced in 1.67 is
still available with the '-key" option.)_
## Highlights:
@@ -375,10 +378,14 @@ help with this or other problems.
* Since v 1.67, the UxPlay option "`-pin`" allows clients to "pair" with the UxPlay server
the first time they connect to it, by entering
a 4-digit pin code that is displayed on the UxPlay terminal. (This is optional, but sometimes required if the client is a
corporately-owned and -managed device with MDM Mobile Device Management.) Pairing occurs just once, is curently only
corporately-owned and -managed device with MDM Mobile Device Management.) Pairing occurs just once, is currently only
recorded in the client, and persists unless the
UxPlay public key (stored in $HOME/.uxplay.pem, or elsewhere if option `-key <filename>` is used) is moved or deleted, after
which a new key is generated. (Non-Apple clients might not implement the persistence feature.)
UxPlay public key is changed. By default (since v1.68) the public key is now generated using the "Device ID", which is either the server's
hardware MAC address, or
can be set with the -m option (most conveniently using the startup option file). (Storage of a more securely-generated
persistent key as an OpenSSL "pem" file is still available with the -key option). For use of uxplay in a more public environment, a
list of previously-registered clients can (since v1.68) be optionally-maintained using the -reg option: without this
option, returning clients claiming to be registered are just trusted and not checked.
* By default, UxPlay is locked to
its current client until that client drops the connection; since UxPlay-1.58, the option `-nohold` modifies this
@@ -708,6 +715,12 @@ with "`#`" are treated as comments, and ignored. Command line options supersede
client will not have to re-authenticate after an initial authentication. _(Add a "pin" entry in the UxPlay startup file if you wish the
UxPlay server to use this protocol)._
**-reg [_filename_]**: (since v1.68). This option maintains a list of previously-pin-registered clients in $HOME/.uxplay.register (or optionally, in _filename_).
Without this option, returning clients claiming to be already pin-registered are trusted and not checked. (This option may be useful if UxPlay is used
in a more public environment, to record client details; the register is text, one line per client, with client's public
key (base-64 format), Device ID, and Device name.)
**-vsync [x]** (In Mirror mode:) this option (**now the default**) uses timestamps to synchronize audio with video on the server,
with an optional audio delay in (decimal) milliseconds (_x_ = "20.5" means 0.0205 seconds delay: positive or
negative delays less than a second are allowed.) It is needed on low-power systems such as Raspberry Pi without hardware
@@ -891,7 +904,16 @@ which will not work if a firewall is running.
a random MAC address will be used even if option **-m** was not specified.
(Note that a random MAC address will be different each time UxPlay is started).
**-key [_filename_]**: By default, the storage of the Server private key is in the file $HOME/.uxplay.pem. Use
**-key [_filename_]**: This (more secure) option for generating and storing a persistant public key (needed for
the -pin option) has been replaced by default with a (less secure) method which generates a key from the server's "device ID"
(MAC address, which can be changed with the -m option, conveniently as a startup file option).
When the -key option is used, a securely generated keypair is generated and stored in `$HOME/.uxplay.pem`, if that file does not exist,
or read from it, if it exists. (Optionally, the key can be stored in _filename_.) This method is more secure than the new default method,
(because the Device ID is broadcast in the DNS_SD announcement) but still leaves the private key exposed to anyone who can access the pem file.
Because the default (but "less-secure") "Device ID" method is simpler, and security of client access to uxplay is unlikely to be an important issue,
the -key option is no longer recommended.
By default, the storage of the Server private key is in the file $HOME/.uxplay.pem. Use
the "-key _filename_" option to change this location. This option should be set in the UxPlay startup file
as a line "`key filename`" (no initial "-"), where ``filename`` is a full path. The filename may be enclosed
in quotes (`"...."`), (and must be, if the filename has any blank spaces).
@@ -1156,6 +1178,11 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
1.68 2023-12-25 Introduced a simpler (default) method for generating a persistent public key from the server MAC
address (which can now be set with the -m option).
(The previous pem-file method is still available with -key option).
New option -reg to maintain a register of pin-authenticated clients.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin":
(uses SRP6a authentication protocol and public key persistence). Detection with error message
of (currently) unsupported H265 video when requesting high resolution over wired ethernet.

View File

@@ -1,10 +1,14 @@
# UxPlay 1.67: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
# UxPlay 1.68: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).
- ***NEW in v1.67**: support for one-time Apple-style "pin" code
client authentication ("client-server pairing") when the option
"-pin" is used.*
- ***NEW in v1.68**: improved support for one-time Apple-style "pin"
codes introduced in 1.67: a register of pin-registered clients is
now optionally maintained to check returning clients; a simpler
method for generating a persistent public key (based on the MAC
address, which now can be set in the UxPlay startup file) is now the
default. (The pem-file method introduced in 1.67 is still available
with the '-key" option.)*
## Highlights:
@@ -451,11 +455,18 @@ below for help with this or other problems.
entering a 4-digit pin code that is displayed on the UxPlay
terminal. (This is optional, but sometimes required if the client is
a corporately-owned and -managed device with MDM Mobile Device
Management.) Pairing occurs just once, is curently only recorded in
the client, and persists unless the UxPlay public key (stored in
\$HOME/.uxplay.pem, or elsewhere if option `-key <filename>` is
used) is moved or deleted, after which a new key is generated.
(Non-Apple clients might not implement the persistence feature.)
Management.) Pairing occurs just once, is currently only recorded in
the client, and persists unless the UxPlay public key is changed. By
default (since v1.68) the public key is now generated using the
"Device ID", which is either the server's hardware MAC address, or
can be set with the -m option (most conveniently using the startup
option file). (Storage of a more securely-generated persistent key
as an OpenSSL "pem" file is still available with the -key option).
For use of uxplay in a more public environment, a list of
previously-registered clients can (since v1.68) be
optionally-maintained using the -reg option: without this option,
returning clients claiming to be registered are just trusted and not
checked.
- By default, UxPlay is locked to its current client until that client
drops the connection; since UxPlay-1.58, the option `-nohold`
@@ -895,6 +906,14 @@ re-authenticate after an initial authentication. *(Add a "pin" entry in
the UxPlay startup file if you wish the UxPlay server to use this
protocol).*
**-reg \[*filename*\]**: (since v1.68). This option maintains a list of
previously-pin-registered clients in \$HOME/.uxplay.register (or
optionally, in *filename*). Without this option, returning clients
claiming to be already pin-registered are trusted and not checked. (This
option may be useful if UxPlay is used in a more public environment, to
record client details; the register is text, one line per client, with
client's public key (base-64 format), Device ID, and Device name.)
**-vsync \[x\]** (In Mirror mode:) this option (**now the default**)
uses timestamps to synchronize audio with video on the server, with an
optional audio delay in (decimal) milliseconds (*x* = "20.5" means
@@ -1125,12 +1144,27 @@ network interface detected) a random MAC address will be used even if
option **-m** was not specified. (Note that a random MAC address will be
different each time UxPlay is started).
**-key \[*filename*\]**: By default, the storage of the Server private
key is in the file \$HOME/.uxplay.pem. Use the "-key *filename*" option
to change this location. This option should be set in the UxPlay startup
file as a line "`key filename`" (no initial "-"), where `filename` is a
full path. The filename may be enclosed in quotes (`"...."`), (and must
be, if the filename has any blank spaces).
**-key \[*filename*\]**: This (more secure) option for generating and
storing a persistant public key (needed for the -pin option) has been
replaced by default with a (less secure) method which generates a key
from the server's "device ID" (MAC address, which can be changed with
the -m option, conveniently as a startup file option). When the -key
option is used, a securely generated keypair is generated and stored in
`$HOME/.uxplay.pem`, if that file does not exist, or read from it, if it
exists. (Optionally, the key can be stored in *filename*.) This method
is more secure than the new default method, (because the Device ID is
broadcast in the DNS_SD announcement) but still leaves the private key
exposed to anyone who can access the pem file. Because the default (but
"less-secure") "Device ID" method is simpler, and security of client
access to uxplay is unlikely to be an important issue, the -key option
is no longer recommended.
By default, the storage of the Server private key is in the file
\$HOME/.uxplay.pem. Use the "-key *filename*" option to change this
location. This option should be set in the UxPlay startup file as a line
"`key filename`" (no initial "-"), where `filename` is a full path. The
filename may be enclosed in quotes (`"...."`), (and must be, if the
filename has any blank spaces).
**-dacp \[*filename*\]**: Export current client DACP-ID and
Active-Remote key to file: default is \$HOME/.uxplay.dacp. (optionally
@@ -1497,6 +1531,12 @@ what version UxPlay claims to be.
# Changelog
1.68 2023-12-25 Introduced a simpler (default) method for generating a
persistent public key from the server MAC address (which can now be set
with the -m option). (The previous pem-file method is still available
with -key option). New option -reg to maintain a register of
pin-authenticated clients.
1.67 2023-11-30 Add support for Apple-style one-time pin authentication
of clients with option "-pin": (uses SRP6a authentication protocol and
public key persistence). Detection with error message of (currently)

View File

@@ -35,6 +35,8 @@
#include "utils.h"
#define SALT_PK "UxPlay-Persistent-Not-Secure-Public-Key"
struct aes_ctx_s {
EVP_CIPHER_CTX *cipher_ctx;
uint8_t key[AES_128_BLOCK_SIZE];
@@ -352,7 +354,7 @@ struct ed25519_key_s {
EVP_PKEY *pkey;
};
ed25519_key_t *ed25519_key_generate(const char *keyfile, int *result) {
ed25519_key_t *ed25519_key_generate(const char *device_id, const char *keyfile, int *result) {
ed25519_key_t *key;
EVP_PKEY_CTX *pctx;
BIO *bp;
@@ -379,7 +381,15 @@ ed25519_key_t *ed25519_key_generate(const char *keyfile, int *result) {
new_pk = true;
}
} else {
new_pk = true;
/* generate (insecure) persistent keypair using device_id */
unsigned char hash[SHA512_DIGEST_LENGTH];
char salt[] = SALT_PK;
sha_ctx_t *ctx = sha_init();
sha_update(ctx, (const unsigned char *) salt, (unsigned int) strlen(salt));
sha_update(ctx, (const unsigned char *) device_id, (unsigned int) strlen(device_id));
sha_final(ctx, hash, NULL);
sha_destroy(ctx);
key->pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, (const unsigned char *) hash, ED25519_KEY_SIZE);
}
if (new_pk) {
@@ -546,3 +556,27 @@ void sha_destroy(sha_ctx_t *ctx) {
int get_random_bytes(unsigned char *buf, int num) {
return RAND_bytes(buf, num);
}
#include <stdio.h>
void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len) {
memset(pk_base64, 0, len);
int len64 = (4 * (pk_len /3)) + (pk_len % 3 ? 4 : 0);
assert (len > len64);
BIO *b64 = BIO_new(BIO_f_base64());
BIO *bio = BIO_new(BIO_s_mem());
BUF_MEM *bufferPtr;
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, pk, pk_len);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
BIO_set_close(bio, BIO_NOCLOSE);
BIO_free_all(bio);
memcpy(pk_base64,(*bufferPtr).data, len64);
}

View File

@@ -67,6 +67,7 @@ x25519_key_t *x25519_key_from_raw(const unsigned char data[X25519_KEY_SIZE]);
void x25519_key_get_raw(unsigned char data[X25519_KEY_SIZE], const x25519_key_t *key);
void x25519_key_destroy(x25519_key_t *key);
int get_random_bytes(unsigned char *buf, int num);
void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len);
void x25519_derive_secret(unsigned char secret[X25519_KEY_SIZE], const x25519_key_t *ours, const x25519_key_t *theirs);
@@ -82,7 +83,7 @@ int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *pl
typedef struct ed25519_key_s ed25519_key_t;
ed25519_key_t *ed25519_key_generate(const char * keyfile, int * result);
ed25519_key_t *ed25519_key_generate(const char *device_id, const char * keyfile, int * result);
ed25519_key_t *ed25519_key_from_raw(const unsigned char data[ED25519_KEY_SIZE]);
void ed25519_key_get_raw(unsigned char data[ED25519_KEY_SIZE], const ed25519_key_t *key);
/*

View File

@@ -84,7 +84,7 @@ derive_key_internal(pairing_session_t *session, const unsigned char *salt, unsig
}
pairing_t *
pairing_init_generate(const char * keyfile, int *result)
pairing_init_generate(const char *device_id, const char *keyfile, int *result)
{
pairing_t *pairing;
*result = 0;
@@ -93,7 +93,7 @@ pairing_init_generate(const char * keyfile, int *result)
return NULL;
}
pairing->ed = ed25519_key_generate(keyfile, result);
pairing->ed = ed25519_key_generate(device_id, keyfile, result);
return pairing;
}
@@ -136,7 +136,7 @@ pairing_session_init(pairing_t *pairing)
session->status = STATUS_INITIAL;
session->srp = NULL;
session->pair_setup = false;
return session;
}
@@ -447,8 +447,20 @@ srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing,
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;
void access_client_session_data(pairing_session_t *session, char **username, char **client_pk64, bool *setup) {
int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
setup = &(session->pair_setup);
*username = session->username;
if (setup) {
*client_pk64 = (char *) malloc(len64);
pk_to_base64(session->client_pk, ED25519_KEY_SIZE, *client_pk64, len64);
} else {
*client_pk64 = NULL;
}
}
void ed25519_pk_to_base64(const unsigned char *pk, char **pk64) {
int len64 = 4 * (1 + (ED25519_KEY_SIZE / 3)) + 1;
*pk64 = (char *) malloc(len64);
pk_to_base64(pk, ED25519_KEY_SIZE, *pk64, len64);
}

View File

@@ -36,7 +36,7 @@
typedef struct pairing_s pairing_t;
typedef struct pairing_session_s pairing_session_t;
pairing_t *pairing_init_generate(const char * keyfile, int *result);
pairing_t *pairing_init_generate(const char *device_id, const char *keyfile, int *result);
void pairing_get_public_key(pairing_t *pairing, unsigned char public_key[ED25519_KEY_SIZE]);
pairing_session_t *pairing_session_init(pairing_t *pairing);
@@ -60,5 +60,6 @@ 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);
void access_client_session_data(pairing_session_t *session, char **username, char **client_pk, bool *setup);
void ed25519_pk_to_base64(const unsigned char *pk, char **pk64);
#endif

View File

@@ -411,7 +411,7 @@ conn_destroy(void *ptr) {
}
raop_t *
raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile) {
raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile) {
raop_t *raop;
pairing_t *pairing;
httpd_t *httpd;
@@ -443,7 +443,7 @@ raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile) {
/* create a new public key for pairing */
int new_key;
pairing = pairing_init_generate(keyfile, &new_key);
pairing = pairing_init_generate(device_id, keyfile, &new_key);
if (!pairing) {
free(raop);
return NULL;

View File

@@ -60,7 +60,7 @@ 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);
void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name);
bool (*check_register) (void *cls, const char *pk_str);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
};
@@ -68,7 +68,7 @@ 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,
unsigned short timing_rport, timing_protocol_t *time_protocol);
RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks, const char* keyfile);
RAOP_API raop_t *raop_init(int max_clients, raop_callbacks_t *callbacks, const char *device_id, const char *keyfile);
RAOP_API void raop_set_log_level(raop_t *raop, int level);
RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls);
RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value);

View File

@@ -341,15 +341,6 @@ 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 {
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);
@@ -442,12 +433,15 @@ raop_handler_pairverify(raop_conn_t *conn,
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);
if (conn->raop->callbacks.check_register) {
const unsigned char *pk = data + 4 + X25519_KEY_SIZE;
char *pk64;
ed25519_pk_to_base64(pk, &pk64);
registered_client = conn->raop->callbacks.check_register(conn->raop->callbacks.cls, pk64);
free (pk64);
}
free (pk_str);
if (!registered_client) {
return;
}
@@ -584,12 +578,32 @@ raop_handler_setup(raop_conn_t *conn,
if (conn->raop->callbacks.report_client_request) {
conn->raop->callbacks.report_client_request(conn->raop->callbacks.cls, deviceID, model, name, &admit_client);
}
free (deviceID);
deviceID = NULL;
free (model);
model = NULL;
free (name);
name = NULL;
if (admit_client && deviceID && name && conn->raop->callbacks.register_client) {
bool pending_registration;
char *client_device_id;
char *client_pk; /* encoded as null-terminated base64 string*/
access_client_session_data(conn->session, &client_device_id, &client_pk, &pending_registration);
if (pending_registration) {
if (client_pk && !strcmp(deviceID, client_device_id)) {
conn->raop->callbacks.register_client(conn->raop->callbacks.cls, client_device_id, client_pk, name);
}
}
if (client_pk) {
free (client_pk);
}
}
if (deviceID) {
free (deviceID);
deviceID = NULL;
}
if (model) {
free (model);
model = NULL;
}
if (name) {
free (name);
name = NULL;
}
if (admit_client == false) {
/* client is not authorized to connect */
plist_free(res_root_node);

View File

@@ -1,11 +1,11 @@
.TH UXPLAY "1" "November 2023" "1.67" "User Commands"
.TH UXPLAY "1" "December 2023" "1.68" "User Commands"
.SH NAME
uxplay \- start AirPlay server
.SH SYNOPSIS
.B uxplay
[\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...]
.SH DESCRIPTION
UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
UxPlay 1.68: An open\-source AirPlay mirroring (+ audio streaming) server:
.SH OPTIONS
.TP
.B
@@ -17,6 +17,10 @@ UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
.IP
without option, pin is random: optionally use fixed pin xxxx.
.TP
\fB\-reg\fI [fn]\fR Keep a register in $HOME/.uxplay.register to verify returning
.IP
client pin-registration; (option: use file "fn" for this)
.TP
\fB\-vsync\fI[x]\fR Mirror mode: sync audio to video using timestamps (default)
.IP
\fIx\fR is optional audio delay: millisecs, decimal, can be neg.
@@ -106,9 +110,13 @@ UxPlay 1.67: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-r\fR {R|L} Rotate 90 degrees Right (cw) or Left (ccw)
.TP
\fB\-m\fR Use random MAC address (use for concurrent UxPlay's)
\fB\-m\fI [mac]\fR Set MAC address (also Device ID); use for concurrent UxPlays
.IP
if mac xx:xx:xx:xx:xx:xx is not given, a random MAC is used.
.PP
.TP
\fB\-key\fI fn \fR Store private key in file fn (Default: $HOME/.uxplay.pem)
\fB\-key\fI [fn]\fR Store private key in $HOME/.uxplay.pem (or in file "fn")
.PP
.TP
\fB\-dacp\fI [fn]\fRExport client DACP information to file $HOME/.uxplay.dacp
.IP

View File

@@ -26,6 +26,7 @@
#include <unistd.h>
#include <ctype.h>
#include <string>
#include <algorithm>
#include <vector>
#include <fstream>
#include <sstream>
@@ -60,7 +61,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.67"
#define VERSION "1.68"
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL
@@ -132,6 +133,10 @@ static unsigned short pin = 0;
static std::string keyfile = "";
static std::string mac_address = "";
static std::string dacpfile = "";
static bool registration_list = false;
static std::string pairing_register = "";
static std::vector <std::string> registered_keys;
/* logging */
static void log(int level, const char* format, ...) {
@@ -561,7 +566,9 @@ static void print_info (char *name) {
printf("-n name Specify the network name of the AirPlay server\n");
printf("-nh Do not add \"@hostname\" at the end of AirPlay server name\n");
printf("-pin[xxxx]Use a 4-digit pin code to control client access (default: no)\n");
printf(" without option, pin is random: optionally use fixed pin xxxx\n");
printf(" default pin is random: optionally use fixed pin xxxx\n");
printf("-reg [fn] Keep a register in $HOME/.uxplay.register to verify returning\n");
printf(" client pin-registration; (option: use file \"fn\" for this)\n");
printf("-vsync [x]Mirror mode: sync audio to video using timestamps (default)\n");
printf(" x is optional audio delay: millisecs, decimal, can be neg.\n");
printf("-vsync no Switch off audio/(server)video timestamp synchronization \n");
@@ -607,8 +614,8 @@ static void print_info (char *name) {
printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n");
printf("-r {R|L} Rotate 90 degrees Right (cw) or Left (ccw)\n");
printf("-m [mac] Set MAC address (also Device ID);use for concurrent UxPlays\n");
printf(" if mac xx:xx:xx:xx:xx:xx is not given, a random mac is used\n");
printf("-key <fn> Store private key in file <fn> (default:$HOME/.uxplay.pem)\n");
printf(" if mac xx:xx:xx:xx:xx:xx is not given, a random MAC is used\n");
printf("-key [fn] Store private key in $HOME/.uxplay.pem (or in file \"fn\")\n");
printf("-dacp [fn]Export client DACP information to file $HOME/.uxplay.dacp\n");
printf(" (option to use file \"fn\" instead); used for client remote\n");
printf("-vdmp [n] Dump h264 video output to \"fn.h264\"; fn=\"videodump\",change\n");
@@ -1044,6 +1051,17 @@ static void parse_arguments (int argc, char *argv[]) {
}
pin = n + 10000;
}
} else if (arg == "-reg") {
registration_list = true;
pairing_register.erase();
if (i < argc - 1 && *argv[i+1] != '-') {
pairing_register.append(argv[++i]);
const char * fn = pairing_register.c_str();
if (!file_has_write_access(fn)) {
fprintf(stderr, "%s cannot be written to:\noption \"-key <fn>\" must be to a file with write access\n", fn);
exit(1);
}
}
} else if (arg == "-key") {
keyfile.erase();
if (i < argc - 1 && *argv[i+1] != '-') {
@@ -1054,8 +1072,10 @@ static void parse_arguments (int argc, char *argv[]) {
exit(1);
}
} else {
fprintf(stderr, "option \"-key <fn>\" requires a path <fn> to a file for persistent key storage\n");
exit(1);
// fprintf(stderr, "option \"-key <fn>\" requires a path <fn> to a file for persistent key storage\n");
// exit(1);
keyfile.erase();
keyfile.append("0");
}
} else if (arg == "-dacp") {
dacpfile.erase();
@@ -1541,15 +1561,38 @@ 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" void register_client(void *cls, const char *device_id, const char *client_pk, const char *client_name) {
if (!registration_list) {
/* we are not maintaining a list of registered clients */
return;
}
LOGI("registered new client: %s DeviceID = %s PK = \n%s", client_name, device_id, client_pk);
registered_keys.push_back(client_pk);
if (strlen(pairing_register.c_str())) {
FILE *fp = fopen(pairing_register.c_str(), "a");
if (fp) {
fprintf(fp, "%s,%s,%s\n", client_pk, device_id, client_name);
fclose(fp);
}
}
}
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" bool check_register(void *cls, const char *client_pk) {
if (!registration_list) {
/* we are not maintaining a list of registered clients */
return true;
}
LOGD("check returning client registration:\n PK:%s", client_pk);
if (std::find(registered_keys.rbegin(), registered_keys.rend(), client_pk) != registered_keys.rend()) {
LOGD("client registration found");
return true;
} else {
LOGE("returning client's pairing registration not found,\n PK: %s", client_pk);
for (int i = 0; i < registered_keys.size(); i++) {
printf("%s\n", (registered_keys[i]).c_str());
}
return false;
}
}
extern "C" void log_callback (void *cls, int level, const char *msg) {
@@ -1600,7 +1643,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_cbs.export_dacp = export_dacp;
/* set max number of connections = 2 to protect against capture by new client */
raop = raop_init(max_connections, &raop_cbs, keyfile.c_str());
raop = raop_init(max_connections, &raop_cbs, mac_address.c_str(), keyfile.c_str());
if (raop == NULL) {
LOGE("Error initializing raop!");
return -1;
@@ -1825,14 +1868,46 @@ int main (int argc, char *argv[]) {
video_parser.append(BT709_FIX);
}
if (require_password && keyfile == "") {
if (require_password && registration_list) {
if (pairing_register == "") {
const char * homedir = get_homedir();
if (homedir) {
pairing_register = homedir;
pairing_register.append("/.uxplay.register");
}
}
}
/* read in public keys that were previously registered with pair-setup-pin */
if (require_password && registration_list && strlen(pairing_register.c_str())) {
char * line = NULL;
size_t len = 0;
std::string key;
FILE *fp = fopen(pairing_register.c_str(), "r");
int clients = 0;
if (fp) {
while ((getline(&line, &len, fp)) != -1) {
/*32 bytes pk -> base64 -> strlen(pk64) = 44 chars = line[0:43]; remove \n at line[44] */
line[44] = '\0';
registered_keys.push_back(key.assign(line));
clients ++;
}
if (clients) {
LOGI("Register %s lists %d pin-registered clients", pairing_register.c_str(), clients);
}
fclose(fp);
free (line);
}
}
if (require_password && keyfile == "0") {
const char * homedir = get_homedir();
if (homedir) {
keyfile.erase();
keyfile = homedir;
keyfile.append("/.uxplay.pem");
} else {
LOGE("could not determine $HOME: public key wiil no be saved, and so will not be persistent");
LOGE("could not determine $HOME: public key wiil not be saved, and so will not be persistent");
}
}
@@ -1883,7 +1958,6 @@ int main (int argc, char *argv[]) {
LOGI("using randomly-generated MAC address %s",mac_address.c_str());
}
parse_hw_addr(mac_address, server_hw_addr);
mac_address.clear();
if (coverart_filename.length()) {
LOGI("any AirPlay audio cover-art will be written to file %s",coverart_filename.c_str());