diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7167c94f8..7b8a8a252 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -163,3 +163,7 @@ if ( NOT APPLE ) target_include_directories( airplay PRIVATE ${DNSSD_INCLUDE_DIR} ) endif() endif() + +pkg_check_modules(SODIUM REQUIRED libsodium) +include_directories(${SODIUM_INCLUDE_DIRS}) +target_link_libraries(airplay ${SODIUM_LIBRARIES}) diff --git a/lib/crypto.c b/lib/crypto.c index 906a2fe0b..60e66793f 100644 --- a/lib/crypto.c +++ b/lib/crypto.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -580,3 +581,185 @@ void pk_to_base64(const unsigned char *pk, int pk_len, char *pk_base64, int len) memcpy(pk_base64,(*bufferPtr).data, len64); } +// CHACHA + +/* 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, uint8_t * salt, uint8_t * info) +{ + 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 *)salt, strlen((const char *)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 *)info, strlen((const char *)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; +} + +/* Creates the decryption key or the encryption key (or both) for HomeKit-based connections + chacha_setup_keys(ctx, "DataStream-Salt123123123", "DataStream-Output-Encryption-Key", NULL, NULL); + chacha_setup_keys(ctx, "Events-Salt", "Events-Write-Encryption-Key", "Events-Salt", "Events-Read-Encryption-Key") + + Returns: + 0 on success + -1 on decryption key fail + -2 on encryption key fail +*/ +int +chacha_setup_keys(chacha_ctx_t *ctx, + unsigned char *session_key, + unsigned char *decryption_salt, unsigned char *decryption_info, + unsigned char *encryption_salt, unsigned char *encryption_info) +{ + assert(ctx); + + ctx->decryption_counter = 0; + ctx->encryption_counter = 0; + + if (decryption_salt != NULL) { + if (hkdf_extract_expand(ctx->decryption_key, CHACHA_KEY_LEN, session_key, HOMEKIT_SESSION_KEY_LEN, decryption_salt, decryption_info)) { + return -1; + } + } + + if (encryption_salt != NULL) { + if (hkdf_extract_expand(ctx->encryption_key, CHACHA_KEY_LEN, session_key, HOMEKIT_SESSION_KEY_LEN, encryption_salt, encryption_info)) { + return -2; + } + } + + return 0; +} + +#define CHACHA_DEBUG 0 + +int +decrypt_chacha(uint8_t *plain, const uint8_t *cipher, uint16_t cipher_len, chacha_ctx_t *chacha_ctx, const void *ad, uint8_t ad_len, uint8_t *tag, uint8_t tag_len) +{ + EVP_CIPHER_CTX *ctx; + int len; + uint8_t nonce[12] = {0}; + memcpy(nonce + 4, &chacha_ctx->decryption_counter, 8); + chacha_ctx->decryption_counter++; + +#if CHACHA_DEBUG + const unsigned char * aad = ad; + + printf("DECRYPTION INFO:"); + + printf("\n\nCipher: (%d)\n", cipher_len); + for (int i = 0; i < cipher_len; i++) { + printf("%02X ", cipher[i]); + } + + printf("\n\nDecryption key (%d)\n", 32); + for (int i = 0; i < 32; i++) { + printf("%02X ", chacha_ctx->decryption_key[i]); + } + + printf("\n\nAD (%d)\n", ad_len); + for (int i = 0; i < ad_len; i++) { + printf("%02X ", aad[i]); + } + + printf("\n\nTag (%d)\n", tag_len); + for (int i = 0; i < tag_len; i++) { + printf("%02X ", tag[i]); + } + + printf("\n\nNonce\n"); + for (int i = 0; i < NONCE_LENGTH; i++) { + printf("%02X ", nonce[i]); + } +#endif + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, chacha_ctx->decryption_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; +} + +int +encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, chacha_ctx_t *chacha_ctx, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len) +{ + EVP_CIPHER_CTX *ctx; + int len; + uint8_t nonce[12]; + memcpy(nonce + 4, &chacha_ctx->encryption_counter, 8); + chacha_ctx->encryption_counter++; + + if (! (ctx = EVP_CIPHER_CTX_new())) + return -1; + + if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, chacha_ctx->encryption_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; +} \ No newline at end of file diff --git a/lib/crypto.h b/lib/crypto.h index 7bd2d5e6f..b66a0f6b6 100644 --- a/lib/crypto.h +++ b/lib/crypto.h @@ -109,6 +109,27 @@ void sha_final(sha_ctx_t *ctx, uint8_t *out, unsigned int *len); void sha_reset(sha_ctx_t *ctx); void sha_destroy(sha_ctx_t *ctx); +// CHACHA + +#define NONCE_LENGTH 12 +#define CHACHA_KEY_LEN 32 +#define HOMEKIT_SESSION_KEY_LEN 64 + +struct chacha_ctx_s { + unsigned char decryption_key[CHACHA_KEY_LEN]; + uint64_t decryption_counter; + unsigned char encryption_key[CHACHA_KEY_LEN]; + uint64_t encryption_counter; +}; + +typedef struct chacha_ctx_s chacha_ctx_t; + +int chacha_setup_keys(chacha_ctx_t *ctx, unsigned char *session_key, unsigned char *decryption_salt, unsigned char *decryption_info, unsigned char *encryption_salt, unsigned char *encryption_info); + +int decrypt_chacha(uint8_t *plain, const uint8_t *cipher, uint16_t cipher_len, chacha_ctx_t *chacha_ctx, const void *ad, uint8_t ad_len, uint8_t *tag, uint8_t tag_len); + +int encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, chacha_ctx_t *chacha_ctx, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len); + #ifdef __cplusplus } #endif diff --git a/lib/dnssd.c b/lib/dnssd.c index 07259375f..00bbbae1b 100644 --- a/lib/dnssd.c +++ b/lib/dnssd.c @@ -396,6 +396,13 @@ dnssd_get_airplay_txt(dnssd_t *dnssd, int *length) return dnssd->TXTRecordGetBytesPtr(&dnssd->airplay_record); } +const char * +dnssd_get_raop_txt(dnssd_t *dnssd, int *length) +{ + *length = dnssd->TXTRecordGetLength(&dnssd->raop_record); + return dnssd->TXTRecordGetBytesPtr(&dnssd->raop_record); +} + const char * dnssd_get_name(dnssd_t *dnssd, int *length) { diff --git a/lib/http_request.c b/lib/http_request.c index 5a6343ad7..3ca8242b7 100644 --- a/lib/http_request.c +++ b/lib/http_request.c @@ -22,6 +22,7 @@ #include "http_request.h" #include "llhttp/llhttp.h" +#include "crypto.h" struct http_request_s { llhttp_t parser; @@ -39,6 +40,8 @@ struct http_request_s { int datalen; int complete; + + chacha_ctx_t *chacha_ctx; }; static int @@ -309,3 +312,28 @@ http_request_get_header_string(http_request_t *request, char **header_str) assert(p == &(str[len])); return len; } + +void +http_request_begin_encryption(http_request_t *request, chacha_ctx_t *chacha_ctx) { + assert(request); + + request->chacha_ctx = chacha_ctx; +} + +int +http_request_get_encryption(http_request_t *request, chacha_ctx_t **chacha_ctx) { + assert(request); + + if (request->chacha_ctx) { + *chacha_ctx = request->chacha_ctx; + return 1; + } + + return 0; +} + +// int http_request_decrypt(http_request_t *request, char* decryption_key, ) { +// assert(request); + + +// } \ No newline at end of file diff --git a/lib/http_request.h b/lib/http_request.h index d72a6f11f..36bc32a99 100644 --- a/lib/http_request.h +++ b/lib/http_request.h @@ -17,6 +17,7 @@ typedef struct http_request_s http_request_t; +typedef struct chacha_ctx_s chacha_ctx_t; http_request_t *http_request_init(void); @@ -32,6 +33,8 @@ const char *http_request_get_protocol(http_request_t *request); const char *http_request_get_header(http_request_t *request, const char *name); const char *http_request_get_data(http_request_t *request, int *datalen); int http_request_get_header_string(http_request_t *request, char **header_str); +void http_request_begin_encryption(http_request_t *request, chacha_ctx_t *ctx); +int http_request_get_encryption(http_request_t *request, chacha_ctx_t **ctx); void http_request_destroy(http_request_t *request); diff --git a/lib/httpd.c b/lib/httpd.c index 7140e2e9b..9fa0b62c5 100644 --- a/lib/httpd.c +++ b/lib/httpd.c @@ -26,6 +26,7 @@ #include "http_request.h" #include "compat.h" #include "logger.h" +#include "crypto.h" struct http_connection_s { int connected; @@ -34,6 +35,8 @@ struct http_connection_s { void *user_data; connection_type_t type; http_request_t *request; + + chacha_ctx_t *chacha_ctx; }; typedef struct http_connection_s http_connection_t; @@ -147,6 +150,10 @@ httpd_remove_connection(httpd_t *httpd, http_connection_t *connection) connection->connected = 0; connection->user_data = NULL; connection->type = CONNECTION_TYPE_UNKNOWN; + if (connection->chacha_ctx) { + free(connection->chacha_ctx); + connection->chacha_ctx = NULL; + } httpd->open_connections--; } @@ -347,6 +354,30 @@ httpd_thread(void *arg) continue; } + if (connection->chacha_ctx) { + unsigned char * buf = buffer; + uint16_t block_len = ret - 18; + int expected_payload_len = 2 + block_len + 16; + if (ret < expected_payload_len) { + logger_log(httpd->logger, LOGGER_INFO, "Incomplete payload, waiting for more data"); + continue; + } else if (ret > expected_payload_len) { + logger_log(httpd->logger, LOGGER_INFO, "Received multiple packets"); + } + int encrypted_len = block_len; + unsigned char tag[16]; + memcpy(tag, buf + block_len + 2, 16); + unsigned char * result = malloc(encrypted_len); + uint16_t header; + memcpy(&header, buf, 2); + + if (decrypt_chacha(result, buf + 2, block_len, connection->chacha_ctx, buf, 2, tag, 16) != 0) { + logger_log(httpd->logger, LOGGER_ERR, "failed to decrypt packet"); + } + memcpy(buffer, result, encrypted_len); + ret = encrypted_len; + } + /* Parse HTTP request from data read from connection */ http_request_add_data(connection->request, buffer, ret); if (http_request_has_error(connection->request)) { @@ -359,7 +390,7 @@ httpd_thread(void *arg) if (http_request_is_complete(connection->request)) { http_response_t *response = NULL; // Callback the received data to raop - if (logger_debug) { + if (logger_debug) { const char *method = http_request_get_method(connection->request); const char *url = http_request_get_url(connection->request); const char *protocol = http_request_get_protocol(connection->request); @@ -367,6 +398,12 @@ httpd_thread(void *arg) "method = %s, url = %s, protocol = %s", connection->socket_fd, i, method, url, protocol); } httpd->callbacks.conn_request(connection->user_data, connection->request, &response); + + chacha_ctx_t *chacha_ctx = NULL; + if (http_request_get_encryption(connection->request, &chacha_ctx)) { + connection->chacha_ctx = chacha_ctx; + } + http_request_destroy(connection->request); connection->request = NULL; @@ -379,6 +416,18 @@ httpd_thread(void *arg) /* Get response data and datalen */ data = http_response_get_data(response, &datalen); + if (connection->chacha_ctx && !chacha_ctx) { // Check that encryption is valid, but that it was not set up on this frame (as this frame shouldn't be encrypted as it will be the final /pair-setup) + unsigned char auth_tag[16]; + int total_packet_len = 2 + datalen + 16; + unsigned char * encrypted = malloc(total_packet_len); // 2 bytes for payload size; datalen = payload; 16 bytes for authTag + memcpy(encrypted, &datalen, 2); + + encrypt_chacha(encrypted + 2, data, datalen, connection->chacha_ctx, encrypted, 2, auth_tag, 16); + memcpy(encrypted + 2 + datalen, auth_tag, 16); + data = encrypted; + datalen = total_packet_len; + } + written = 0; while (written < datalen) { ret = send(connection->socket_fd, data+written, datalen-written, 0); diff --git a/lib/mirror_buffer.c b/lib/mirror_buffer.c index b88fe244a..8870fb75f 100644 --- a/lib/mirror_buffer.c +++ b/lib/mirror_buffer.c @@ -31,6 +31,7 @@ struct mirror_buffer_s { logger_t *logger; aes_ctx_t *aes_ctx; + chacha_ctx_t *chacha_ctx; int nextDecryptCount; uint8_t og[16]; /* audio aes key is used in a hash for the video aes key and iv */ @@ -67,6 +68,19 @@ mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, const uint64_t *streamCon mirror_buffer->aes_ctx = aes_ctr_init(aeskey_video, aesiv_video); } +void +mirror_buffer_init_chacha(mirror_buffer_t *mirror_buffer, unsigned char *session_key, const uint64_t *streamConnectionID) +{ + unsigned char stream_key[32]; + int stream_key_len = 32; + char stream_key_salt[16 + 20]; // Needs to be large enough to hold "DataStream-Salt" + streamConnectionID as a str + int ret = snprintf( stream_key_salt, sizeof(stream_key_salt), "%s%llu", "DataStream-Salt", *streamConnectionID ); + + mirror_buffer->chacha_ctx = malloc(sizeof(chacha_ctx_t)); + + chacha_setup_keys(mirror_buffer->chacha_ctx, session_key, stream_key_salt, "DataStream-Output-Encryption-Key", NULL, NULL); +} + mirror_buffer_t * mirror_buffer_init(logger_t *logger, const unsigned char *aeskey) { @@ -82,35 +96,45 @@ mirror_buffer_init(logger_t *logger, const unsigned char *aeskey) return mirror_buffer; } -void mirror_buffer_decrypt(mirror_buffer_t *mirror_buffer, unsigned char* input, unsigned char* output, int inputLen) { +void mirror_buffer_decrypt(mirror_buffer_t *mirror_buffer, unsigned char packet_header[128], unsigned char* input, unsigned char* output, int *input_len) { + int inputLen = *input_len; // Start decrypting - if (mirror_buffer->nextDecryptCount > 0) {//mirror_buffer->nextDecryptCount = 10 - for (int i = 0; i < mirror_buffer->nextDecryptCount; i++) { - output[i] = (input[i] ^ mirror_buffer->og[(16 - mirror_buffer->nextDecryptCount) + i]); + if (!mirror_buffer->chacha_ctx) { + if (mirror_buffer->nextDecryptCount > 0) {//mirror_buffer->nextDecryptCount = 10 + for (int i = 0; i < mirror_buffer->nextDecryptCount; i++) { + output[i] = (input[i] ^ mirror_buffer->og[(16 - mirror_buffer->nextDecryptCount) + i]); + } + } + // Handling encrypted bytes + int encryptlen = ((inputLen - mirror_buffer->nextDecryptCount) / 16) * 16; + // Aes decryption + aes_ctr_start_fresh_block(mirror_buffer->aes_ctx); + aes_ctr_decrypt(mirror_buffer->aes_ctx, input + mirror_buffer->nextDecryptCount, + input + mirror_buffer->nextDecryptCount, encryptlen); + // Copy to output + memcpy(output + mirror_buffer->nextDecryptCount, input + mirror_buffer->nextDecryptCount, encryptlen); + // int outputlength = mirror_buffer->nextDecryptCount + encryptlen; + // Processing remaining length + int restlen = (inputLen - mirror_buffer->nextDecryptCount) % 16; + int reststart = inputLen - restlen; + mirror_buffer->nextDecryptCount = 0; + if (restlen > 0) { + memset(mirror_buffer->og, 0, 16); + memcpy(mirror_buffer->og, input + reststart, restlen); + aes_ctr_decrypt(mirror_buffer->aes_ctx, mirror_buffer->og, mirror_buffer->og, 16); + for (int j = 0; j < restlen; j++) { + output[reststart + j] = mirror_buffer->og[j]; + } + //outputlength += restlen; + mirror_buffer->nextDecryptCount = 16 - restlen;// Difference 16-6=10 bytes } } - // Handling encrypted bytes - int encryptlen = ((inputLen - mirror_buffer->nextDecryptCount) / 16) * 16; - // Aes decryption - aes_ctr_start_fresh_block(mirror_buffer->aes_ctx); - aes_ctr_decrypt(mirror_buffer->aes_ctx, input + mirror_buffer->nextDecryptCount, - input + mirror_buffer->nextDecryptCount, encryptlen); - // Copy to output - memcpy(output + mirror_buffer->nextDecryptCount, input + mirror_buffer->nextDecryptCount, encryptlen); - // int outputlength = mirror_buffer->nextDecryptCount + encryptlen; - // Processing remaining length - int restlen = (inputLen - mirror_buffer->nextDecryptCount) % 16; - int reststart = inputLen - restlen; - mirror_buffer->nextDecryptCount = 0; - if (restlen > 0) { - memset(mirror_buffer->og, 0, 16); - memcpy(mirror_buffer->og, input + reststart, restlen); - aes_ctr_decrypt(mirror_buffer->aes_ctx, mirror_buffer->og, mirror_buffer->og, 16); - for (int j = 0; j < restlen; j++) { - output[reststart + j] = mirror_buffer->og[j]; + else { + if (decrypt_chacha(output, input, inputLen - 16, mirror_buffer->chacha_ctx, packet_header, 128, input + inputLen - 16, 16)) { + printf("ERROR: Failed to decrypt packet"); } - //outputlength += restlen; - mirror_buffer->nextDecryptCount = 16 - restlen;// Difference 16-6=10 bytes + + *input_len -= 16; } } diff --git a/lib/mirror_buffer.h b/lib/mirror_buffer.h index 2b65ab08a..ebdb2c97e 100644 --- a/lib/mirror_buffer.h +++ b/lib/mirror_buffer.h @@ -26,6 +26,7 @@ typedef struct mirror_buffer_s mirror_buffer_t; mirror_buffer_t *mirror_buffer_init( logger_t *logger, const unsigned char *aeskey); void mirror_buffer_init_aes(mirror_buffer_t *mirror_buffer, const uint64_t *streamConnectionID); -void mirror_buffer_decrypt(mirror_buffer_t *raop_mirror, unsigned char* input, unsigned char* output, int datalen); +void mirror_buffer_init_chacha(mirror_buffer_t *mirror_buffer, unsigned char *session_key, const uint64_t *streamConnectionID); +void mirror_buffer_decrypt(mirror_buffer_t *mirror_buffer, unsigned char packet_header[128], unsigned char* input, unsigned char* output, int *input_len); void mirror_buffer_destroy(mirror_buffer_t *mirror_buffer); #endif //MIRROR_BUFFER_H diff --git a/lib/pairing.c b/lib/pairing.c index e60273cd7..0e216a38d 100644 --- a/lib/pairing.c +++ b/lib/pairing.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -226,6 +227,11 @@ pairing_session_get_signature(pairing_session_t *session, unsigned char signatur return 0; } +int +pairing_session_get_session_key(pairing_session_t *session, unsigned char ** session_key) { + *session_key = session->srp->session_key; +} + int pairing_session_finish(pairing_session_t *session, const unsigned char signature[PAIRING_SIG_SIZE]) { @@ -308,13 +314,10 @@ random_pin() { int srp_new_user(pairing_session_t *session, pairing_t *pairing, const char *device_id, const char *pin, const char **salt, int *len_salt, const char **pk, int *len_pk) { - if (strlen(device_id) > SRP_USERNAME_SIZE) { - return -1; - } - strncpy(session->username, device_id, SRP_USERNAME_SIZE); + strncpy(session->username, device_id, 10); - if (session->srp) { + if (session->srp) { free (session->srp); session->srp = NULL; } @@ -326,6 +329,7 @@ srp_new_user(pairing_session_t *session, pairing_t *pairing, const char *device_ get_random_bytes(session->srp->private_key, SRP_PRIVATE_KEY_SIZE); const unsigned char *srp_b = session->srp->private_key; + unsigned char * srp_B; unsigned char * srp_s; unsigned char * srp_v; @@ -395,8 +399,10 @@ srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigne memcpy(session->srp->session_key, session_key, len_K); memcpy(proof, M2, proof_len); srp_verifier_delete(verifier); + return 0; } + int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsigned char *epk, unsigned char *auth_tag) { diff --git a/lib/pairing.h b/lib/pairing.h index 54db62b17..f8a7c02e9 100644 --- a/lib/pairing.h +++ b/lib/pairing.h @@ -21,13 +21,14 @@ #define PAIRING_SIG_SIZE (2 * X25519_KEY_SIZE) -#define SRP_USERNAME_SIZE 24 /* accomodates up to an 8-octet MAC address */ -#define SRP_SESSION_KEY_SIZE 40 -#define SRP_VERIFIER_SIZE 256 +#define SRP_USERNAME "Pair-Setup" +#define SRP_USERNAME_SIZE 10 +#define SRP_SESSION_KEY_SIZE 64 +#define SRP_VERIFIER_SIZE 384 #define SRP_SALT_SIZE 16 -#define SRP_PK_SIZE 256 -#define SRP_SHA SRP_SHA1 -#define SRP_NG SRP_NG_2048 +#define SRP_PK_SIZE 384 +#define SRP_SHA SRP_SHA512 +#define SRP_NG SRP_NG_3072 #define SRP_M2_SIZE 64 #define SRP_PRIVATE_KEY_SIZE 32 #define GCM_AUTHTAG_SIZE 16 @@ -45,6 +46,7 @@ int pairing_session_check_handshake_status(pairing_session_t *session); int pairing_session_handshake(pairing_session_t *session, const unsigned char ecdh_key[X25519_KEY_SIZE], const unsigned char ed_key[ED25519_KEY_SIZE]); int pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_key[X25519_KEY_SIZE]); +int pairing_session_get_session_key(pairing_session_t *session, unsigned char ** session_key); int random_pin(); int pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE]); int pairing_session_finish(pairing_session_t *session, const unsigned char signature[PAIRING_SIG_SIZE]); diff --git a/lib/raop.c b/lib/raop.c index af14baf7a..eabe6e3bf 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -177,17 +177,16 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { if (httpd_nohold(conn->raop->httpd)) { logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr); if (conn->raop->callbacks.video_reset) { - printf("**************************video_reset*************************\n"); + printf("**************************video_reset*************************\n"); conn->raop->callbacks.video_reset(conn->raop->callbacks.cls); - } - httpd_remove_known_connections(conn->raop->httpd); - + } + httpd_remove_known_connections(conn->raop->httpd); } else { logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr); *response = http_response_create(); http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client"); goto finish; - } + } } httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP); conn->connection_type = CONNECTION_TYPE_RAOP; @@ -209,20 +208,20 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } /* this rejects unsupported messages from _airplay._tcp for video streaming protocol*/ - if (!cseq) { - return; - } - + // if (!cseq) { + // return; + // } + logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s %s", method, url, protocol); char *header_str= NULL; http_request_get_header_string(request, &header_str); if (header_str) { logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", header_str); - bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL); - bool data_is_text = (strstr(header_str,"text/") != NULL); free(header_str); int request_datalen; const char *request_data = http_request_get_data(request, &request_datalen); + bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL && strstr(request_data, "bplist") != NULL); /* Server sometimes says binary-plist is the content when it's not */ + bool data_is_text = (strstr(header_str,"text/") != NULL); if (request_data && logger_debug) { if (request_datalen > 0) { if (data_is_plist) { @@ -255,7 +254,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url); raop_handler_t handler = NULL; - if (!strcmp(method, "GET") && !strcmp(url, "/info")) { + if (!strcmp(method, "GET") && strstr(url, "/info") != NULL) { handler = &raop_handler_info; } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-pin-start")) { handler = &raop_handler_pairpinstart; @@ -292,7 +291,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } finish:; http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION); - http_response_add_header(*response, "CSeq", cseq); + // http_response_add_header(*response, "CSeq", cseq); http_response_finish(*response, response_data, response_datalen); int len; diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 6493c3330..05c3b56da 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -23,6 +23,8 @@ #include #include #include +#include "http_request.h" +#include "crypto.h" #define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */ #define SECOND_IN_USECS 1000000 @@ -34,7 +36,7 @@ raop_handler_info(raop_conn_t *conn, http_request_t *request, http_response_t *response, char **response_data, int *response_datalen) { - assert(conn->raop->dnssd); + // assert(conn->raop->dnssd); plist_t res_node = plist_new_dict(); @@ -59,6 +61,11 @@ raop_handler_info(raop_conn_t *conn, plist_t txt_airplay_node = plist_new_data(airplay_txt, airplay_txt_len); plist_dict_set_item(res_node, "txtAirPlay", txt_airplay_node); + int raop_txt_len = 0; + const char *raop_txt = dnssd_get_airplay_txt(conn->raop->dnssd, &raop_txt_len); + plist_t txt_raop_node = plist_new_data(raop_txt, raop_txt_len); + plist_dict_set_item(res_node, "txtRAOP", txt_raop_node); + uint64_t features = dnssd_get_airplay_features(conn->raop->dnssd); plist_t features_node = plist_new_uint(features); plist_dict_set_item(res_node, "features", features_node); @@ -105,6 +112,10 @@ raop_handler_info(raop_conn_t *conn, plist_t keep_alive_send_stats_as_body_node = plist_new_uint(1); plist_dict_set_item(res_node, "keepAliveSendStatsAsBody", keep_alive_send_stats_as_body_node); + plist_t features_ex_node = plist_new_string("1c9/St5PFbgmIQQ"); + plist_dict_set_item(res_node, "featuresEx", features_ex_node); + + plist_t audio_latencies_node = plist_new_array(); plist_t audio_latencies_0_node = plist_new_dict(); plist_t audio_latencies_0_output_latency_micros_node = plist_new_bool(0); @@ -142,12 +153,17 @@ raop_handler_info(raop_conn_t *conn, plist_t displays_0_width_node = plist_new_uint(conn->raop->width); plist_t displays_0_height_node = plist_new_uint(conn->raop->height); plist_t displays_0_width_pixels_node = plist_new_uint(conn->raop->width); + plist_t displays_0_max_width_pixels_node = plist_new_uint(3840); plist_t displays_0_height_pixels_node = plist_new_uint(conn->raop->height); + plist_t displays_0_max_height_pixels_node = plist_new_uint(2160); plist_t displays_0_rotation_node = plist_new_bool(0); plist_t displays_0_refresh_rate_node = plist_new_uint(conn->raop->refreshRate); plist_t displays_0_max_fps_node = plist_new_uint(conn->raop->maxFPS); plist_t displays_0_overscanned_node = plist_new_bool(conn->raop->overscanned); plist_t displays_0_features = plist_new_uint(14); + plist_t displays_0_supports444 = plist_new_bool(1); + // unsigned char edid[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0xAE, 0xFB, 0x65, 0x00, 0x00, 0x00, 0x00, 0x33, 0x1D, 0x01, 0x03, 0x80, 0x35, 0x1E, 0x78, 0x2E, 0xFF, 0xD5, 0xA9, 0x53, 0x45, 0xA0, 0x25, 0x0D, 0x50, 0x54, 0xBD, 0xEF, 0x00, 0x81, 0x80, 0x95, 0x00, 0xB3, 0x00, 0xD1, 0xC0, 0xD1, 0x00, 0x71, 0x4F, 0x81, 0x8A, 0x01, 0x01, 0x56, 0x5E, 0x00, 0xA0, 0xA0, 0xA0, 0x29, 0x50, 0x30, 0x20, 0x35, 0x00, 0x0F, 0x28, 0x21, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x30, 0x4B, 0x1E, 0x72, 0x1E, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x4C, 0x45, 0x4E, 0x20, 0x4C, 0x32, 0x34, 0x71, 0x2D, 0x33, 0x30, 0x0A, 0x20, 0x01, 0x07, 0x02, 0x03, 0x27, 0xF1, 0x4B, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x13, 0x90, 0x1F, 0x12, 0x11, 0x23, 0x09, 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0x65, 0x03, 0x0C, 0x00, 0x10, 0x00, 0x68, 0x1A, 0x00, 0x00, 0x01, 0x01, 0x30, 0x4B, 0x00, 0x01, 0x1D, 0x00, 0x72, 0x51, 0xD0, 0x1E, 0x20, 0x6E, 0x28, 0x55, 0x00, 0x0F, 0x28, 0x21, 0x00, 0x00, 0x1E, 0x8C, 0x0A, 0xD0, 0x8A, 0x20, 0xE0, 0x2D, 0x10, 0x10, 0x3E, 0x96, 0x00, 0x0F, 0x28, 0x21, 0x00, 0x00, 0x18, 0xCC, 0x74, 0x00, 0xA0, 0xA0, 0xA0, 0x1E, 0x50, 0x30, 0x20, 0x35, 0x00, 0x0F, 0x28, 0x21, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9 }; + // plist_t displays_0_edid = plist_new_data(edid, sizeof(edid)); plist_dict_set_item(displays_0_node, "uuid", displays_0_uuid_node); plist_dict_set_item(displays_0_node, "widthPhysical", displays_0_width_physical_node); @@ -155,12 +171,16 @@ raop_handler_info(raop_conn_t *conn, plist_dict_set_item(displays_0_node, "width", displays_0_width_node); plist_dict_set_item(displays_0_node, "height", displays_0_height_node); plist_dict_set_item(displays_0_node, "widthPixels", displays_0_width_pixels_node); + plist_dict_set_item(displays_0_node, "widthPixelsMax", displays_0_max_width_pixels_node); plist_dict_set_item(displays_0_node, "heightPixels", displays_0_height_pixels_node); + plist_dict_set_item(displays_0_node, "heightPixelsMax", displays_0_max_height_pixels_node); plist_dict_set_item(displays_0_node, "rotation", displays_0_rotation_node); plist_dict_set_item(displays_0_node, "refreshRate", displays_0_refresh_rate_node); plist_dict_set_item(displays_0_node, "maxFPS", displays_0_max_fps_node); plist_dict_set_item(displays_0_node, "overscanned", displays_0_overscanned_node); plist_dict_set_item(displays_0_node, "features", displays_0_features); + plist_dict_set_item(displays_0_node, "receiverSupports444", displays_0_supports444); + // plist_dict_set_item(displays_0_edid, "edid", displays_0_features); plist_array_append_item(displays_node, displays_0_node); plist_dict_set_item(res_node, "displays", displays_node); @@ -363,24 +383,94 @@ raop_handler_pairsetup(raop_conn_t *conn, char **response_data, int *response_datalen) { unsigned char public_key[ED25519_KEY_SIZE]; - //const char *data; + const char *data; int datalen; - //data = + data = http_request_get_data(request, &datalen); - if (datalen != 32) { - logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-setup data"); - return; + + char* hkpVersion = http_request_get_header(request, "X-Apple-HKP"); + + if (hkpVersion == NULL) { + if (datalen != 32) { + logger_log(conn->raop->logger, LOGGER_ERR, "Invalid pair-setup data"); + return; + } + + logger_log(conn->raop->logger, LOGGER_DEBUG, "PAIRING: Legacy pairing"); + pairing_get_public_key(conn->raop->pairing, public_key); + pairing_session_set_setup_status(conn->session); + + *response_data = malloc(sizeof(public_key)); + if (*response_data) { + http_response_add_header(response, "Content-Type", "application/octet-stream"); + memcpy(*response_data, public_key, sizeof(public_key)); + *response_datalen = sizeof(public_key); + } } + else if (!strcmp(hkpVersion, "4")) { + switch (data[2]) { + case 0x00: // M1 (usually 0x01, 0x06, 0x00, 0x01) + const char *salt; + const char *pk; + int len_pk, len_salt; + char *method = NULL; + int ret = srp_new_user(conn->session, conn->raop->pairing, "Pair-Setup", + "3939", &salt, &len_salt, &pk, &len_pk); + *response_data = malloc(409); + char *res_data = *response_data; + *response_datalen = 409; + + res_data[0] = 0x06; + res_data[1] = 0x01; + res_data[2] = 0x02; + res_data[3] = 0x02; + res_data[4] = 0x10; + memcpy(&res_data[5], salt, len_salt); + res_data[21] = 0x03; + res_data[22] = 0xff; + memcpy(&res_data[23], pk, 255); + res_data[278] = 0x03; + res_data[279] = 0x81; + memcpy(&res_data[280], &pk[255], 0x81); + break; + case 0x03: // M3 + unsigned char *client_pk = malloc(384); + unsigned char proof[64]; + memcpy(client_pk, &data[5], 0xff); + memcpy(&client_pk[0xff], &data[262], 0x81); + memcpy(proof, &data[393], 0x40); + + int ret2 = srp_validate_proof(conn->session, conn->raop->pairing, (const unsigned char *) client_pk, + 384, proof, 0x40, 0x40); + if (ret2 < 0) { + logger_log(conn->raop->logger, LOGGER_ERR, "Client Authentication Failure (client proof not validated) %d", ret2); + } + + unsigned char *session_key; + pairing_session_get_session_key(conn->session, &session_key); - pairing_get_public_key(conn->raop->pairing, public_key); - pairing_session_set_setup_status(conn->session); + chacha_ctx_t *ctx = malloc(sizeof(chacha_ctx_t)); + chacha_setup_keys(ctx, session_key, "Control-Salt", "Control-Write-Encryption-Key", "Control-Salt", "Control-Read-Encryption-Key"); - *response_data = malloc(sizeof(public_key)); - if (*response_data) { - http_response_add_header(response, "Content-Type", "application/octet-stream"); - memcpy(*response_data, public_key, sizeof(public_key)); - *response_datalen = sizeof(public_key); + *response_data = malloc(69); + char *res_data2 = *response_data; + *response_datalen = 69; + + res_data2[0] = 0x06; + res_data2[1] = 0x01; + res_data2[2] = 0x04; + res_data2[3] = 0x04; + res_data2[4] = 0x40; + memcpy(&res_data2[5], proof, 0x40); + + http_request_begin_encryption(request, ctx); + + break; + } + } + else { + logger_log(conn->raop->logger, LOGGER_ERR, "PAIRING: Unimplemented HomeKit pairing version: %s", hkpVersion); } } @@ -540,11 +630,14 @@ raop_handler_setup(raop_conn_t *conn, plist_from_bin(data, data_len, &req_root_node); plist_t req_ekey_node = plist_dict_get_item(req_root_node, "ekey"); plist_t req_eiv_node = plist_dict_get_item(req_root_node, "eiv"); + plist_t test_node = plist_dict_get_item(req_root_node, "timingPort"); // For the response plist_t res_root_node = plist_new_dict(); - if (PLIST_IS_DATA(req_eiv_node) && PLIST_IS_DATA(req_ekey_node)) { + + + if (PLIST_IS_INT(test_node)) { // The first SETUP call that initializes keys and timing unsigned char aesiv[16]; @@ -567,10 +660,10 @@ raop_handler_setup(raop_conn_t *conn, plist_get_string_val(req_model_node, &model); plist_t req_name_node = plist_dict_get_item(req_root_node, "name"); plist_get_string_val(req_name_node, &name); - if (conn->raop->callbacks.report_client_request) { + if (conn->raop->callbacks.report_client_request) { conn->raop->callbacks.report_client_request(conn->raop->callbacks.cls, deviceID, model, name, &admit_client); } - if (admit_client && deviceID && name && conn->raop->callbacks.register_client) { + 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*/ @@ -578,12 +671,12 @@ raop_handler_setup(raop_conn_t *conn, 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; @@ -601,37 +694,37 @@ raop_handler_setup(raop_conn_t *conn, plist_free(res_root_node); plist_free(req_root_node); return; - } - - plist_get_data_val(req_eiv_node, &eiv, &eiv_len); - memcpy(aesiv, eiv, 16); - free(eiv); - logger_log(conn->raop->logger, LOGGER_DEBUG, "eiv_len = %llu", eiv_len); - if (logger_debug) { - char* str = utils_data_to_string(aesiv, 16, 16); - logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aesiv (needed for AES-CBC audio decryption iv):\n%s", str); - free(str); - } + } - char* ekey = NULL; - uint64_t ekey_len = 0; - plist_get_data_val(req_ekey_node, &ekey, &ekey_len); - memcpy(eaeskey,ekey,72); - free(ekey); - logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey_len = %llu", ekey_len); - // eaeskey is 72 bytes, aeskey is 16 bytes - if (logger_debug) { - char *str = utils_data_to_string((unsigned char *) eaeskey, ekey_len, 16); - logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey:\n%s", str); - free (str); - } - int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) eaeskey, aeskey); - logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret); - if (logger_debug) { - char *str = utils_data_to_string(aeskey, 16, 16); - logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey (fairplay-decrypted from ekey):\n%s", str); - free(str); - } + // plist_get_data_val(req_eiv_node, &eiv, &eiv_len); + // memcpy(aesiv, eiv, 16); + // free(eiv); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "eiv_len = %llu", eiv_len); + // if (logger_debug) { + // char* str = utils_data_to_string(aesiv, 16, 16); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aesiv (needed for AES-CBC audio decryption iv):\n%s", str); + // free(str); + // } + + // char* ekey = NULL; + // uint64_t ekey_len = 0; + // plist_get_data_val(req_ekey_node, &ekey, &ekey_len); + // memcpy(eaeskey,ekey,72); + // free(ekey); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey_len = %llu", ekey_len); + // // eaeskey is 72 bytes, aeskey is 16 bytes + // if (logger_debug) { + // char *str = utils_data_to_string((unsigned char *) eaeskey, ekey_len, 16); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "ekey:\n%s", str); + // free (str); + // } + // int ret = fairplay_decrypt(conn->fairplay, (unsigned char*) eaeskey, aeskey); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "fairplay_decrypt ret = %d", ret); + // if (logger_debug) { + // char *str = utils_data_to_string(aeskey, 16, 16); + // logger_log(conn->raop->logger, LOGGER_DEBUG, "16 byte aeskey (fairplay-decrypted from ekey):\n%s", str); + // free(str); + // } const char *user_agent = http_request_get_header(request, "User-Agent"); logger_log(conn->raop->logger, LOGGER_INFO, "Client identified as User-Agent: %s", user_agent); @@ -688,26 +781,26 @@ raop_handler_setup(raop_conn_t *conn, logger_log(conn->raop->logger, LOGGER_ERR, "Client specified AirPlay2 \"Remote Control\" protocol\n" " Only AirPlay v1 protocol (using NTP and timing port) is supported"); } - } + } char *timing_protocol = NULL; - timing_protocol_t time_protocol; + timing_protocol_t time_protocol; plist_t req_timing_protocol_node = plist_dict_get_item(req_root_node, "timingProtocol"); plist_get_string_val(req_timing_protocol_node, &timing_protocol); if (timing_protocol) { - int string_len = strlen(timing_protocol); - if (strncmp(timing_protocol, "NTP", string_len) == 0) { - time_protocol = NTP; - } else if (strncmp(timing_protocol, "None", string_len) == 0) { - time_protocol = TP_NONE; - } else { - time_protocol = TP_OTHER; - } - if (time_protocol != NTP) { - logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s," - " but timingProtocol= NTP is required here", timing_protocol); - } - free (timing_protocol); - timing_protocol = NULL; + int string_len = strlen(timing_protocol); + if (strncmp(timing_protocol, "NTP", string_len) == 0) { + time_protocol = NTP; + } else if (strncmp(timing_protocol, "None", string_len) == 0) { + time_protocol = TP_NONE; + } else { + time_protocol = TP_OTHER; + } + if (time_protocol != NTP) { + logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s," + " but timingProtocol= NTP is required here", timing_protocol); + } + free (timing_protocol); + timing_protocol = NULL; } else { logger_log(conn->raop->logger, LOGGER_DEBUG, "Client did not specify timingProtocol," " old protocol without offset will be used"); @@ -715,8 +808,9 @@ raop_handler_setup(raop_conn_t *conn, } uint64_t timing_rport = 0; plist_t req_timing_port_node = plist_dict_get_item(req_root_node, "timingPort"); - if (req_timing_port_node) { - plist_get_uint_val(req_timing_port_node, &timing_rport); + if (req_timing_port_node) + { + plist_get_uint_val(req_timing_port_node, &timing_rport); } if (timing_rport) { logger_log(conn->raop->logger, LOGGER_DEBUG, "timing_rport = %llu", timing_rport); @@ -746,9 +840,10 @@ raop_handler_setup(raop_conn_t *conn, remote, conn->remotelen, aeskey, aesiv); conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks, conn->raop_ntp, remote, conn->remotelen, aeskey); + printf("RAOP RTP MIRROR: %p", conn->raop_rtp_mirror); - // plist_t res_event_port_node = plist_new_uint(conn->raop->port); - plist_t res_event_port_node = plist_new_uint(0); + // plist_t res_event_port_node = plist_new_uint(conn->raop->port); + plist_t res_event_port_node = plist_new_uint(0); plist_t res_timing_port_node = plist_new_uint(timing_lport); plist_dict_set_item(res_root_node, "timingPort", res_timing_port_node); plist_dict_set_item(res_root_node, "eventPort", res_event_port_node); @@ -776,11 +871,16 @@ raop_handler_setup(raop_conn_t *conn, plist_t stream_id_node = plist_dict_get_item(req_stream_node, "streamConnectionID"); uint64_t stream_connection_id; plist_get_uint_val(stream_id_node, &stream_connection_id); + // test_get_stream_key(conn->session, stream_connection_id); logger_log(conn->raop->logger, LOGGER_DEBUG, "streamConnectionID (needed for AES-CTR video decryption" " key and iv): %llu", stream_connection_id); if (conn->raop_rtp_mirror) { + // raop_rtp_mirror_init_aes(conn->raop_rtp_mirror, &stream_connection_id); + unsigned char *session_key; + pairing_session_get_session_key(conn->session, &session_key); raop_rtp_mirror_init_aes(conn->raop_rtp_mirror, &stream_connection_id); + raop_rtp_mirror_init_chacha(conn->raop_rtp_mirror, session_key, &stream_connection_id); raop_rtp_mirror_start(conn->raop_rtp_mirror, &dport, conn->raop->clientFPSdata); logger_log(conn->raop->logger, LOGGER_DEBUG, "Mirroring initialized successfully"); } else { diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 141c65eb4..9645b4a91 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -38,6 +38,7 @@ #include "stream.h" #include "utils.h" #include "plist/plist.h" +// #include "pairing.h" #ifdef _WIN32 #define CAST (char *) @@ -170,6 +171,12 @@ raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamCon mirror_buffer_init_aes(raop_rtp_mirror->buffer, streamConnectionID); } +void +raop_rtp_mirror_init_chacha(raop_rtp_mirror_t *raop_rtp_mirror, unsigned char *session_key, uint64_t *streamConnectionID) +{ + mirror_buffer_init_chacha(raop_rtp_mirror->buffer, session_key, streamConnectionID); +} + #define RAOP_PACKET_LEN 32768 /** * Mirror @@ -383,7 +390,7 @@ raop_rtp_mirror_thread(void *arg) } unsigned char* payload_out; - unsigned char* payload_decrypted; + unsigned char* payload_decrypted; /* * nal_types:1 Coded non-partitioned slice of a non-IDR picture * 5 Coded non-partitioned slice of an IDR picture @@ -415,13 +422,13 @@ raop_rtp_mirror_thread(void *arg) payload_decrypted = payload_out + sps_pps_len; memcpy(payload_out, sps_pps, sps_pps_len); free (sps_pps); - sps_pps = NULL; + sps_pps = NULL; } else { payload_out = (unsigned char*) malloc(payload_size); payload_decrypted = payload_out; } // Decrypt data - mirror_buffer_decrypt(raop_rtp_mirror->buffer, payload, payload_decrypted, payload_size); + mirror_buffer_decrypt(raop_rtp_mirror->buffer, packet, payload, payload_decrypted, &payload_size); // It seems the AirPlay protocol prepends NALs with their size, which we're replacing with the 4-byte // start code for the NAL Byte-Stream Format. @@ -464,7 +471,7 @@ raop_rtp_mirror_thread(void *arg) case 5: /*IDR, slice_layer_without_partitioning */ case 1: /*non-IDR, slice_layer_without_partitioning */ break; - case 2: /* slice data partition A */ + case 2: /* slice data partition A */ case 3: /* slice data partition B */ case 4: /* slice data partition C */ logger_log(raop_rtp_mirror->logger, LOGGER_INFO, @@ -750,6 +757,8 @@ raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror } //use_ipv6 = 0; + printf("Here 1\n"); + raop_rtp_mirror->mirror_data_lport = *mirror_data_lport; if (raop_rtp_mirror_init_socket(raop_rtp_mirror, use_ipv6) < 0) { logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror initializing socket failed"); diff --git a/lib/raop_rtp_mirror.h b/lib/raop_rtp_mirror.h index 2e7914d61..f83cfb2f5 100644 --- a/lib/raop_rtp_mirror.h +++ b/lib/raop_rtp_mirror.h @@ -28,6 +28,7 @@ typedef struct h264codec_s h264codec_t; raop_rtp_mirror_t *raop_rtp_mirror_init(logger_t *logger, raop_callbacks_t *callbacks, raop_ntp_t *ntp, const char *remote, int remotelen, const unsigned char *aeskey); void raop_rtp_mirror_init_aes(raop_rtp_mirror_t *raop_rtp_mirror, uint64_t *streamConnectionID); +void raop_rtp_mirror_init_chacha(raop_rtp_mirror_t *raop_rtp_mirror, unsigned char *session_key, uint64_t *streamConnectionID); void raop_rtp_mirror_start(raop_rtp_mirror_t *raop_rtp_mirror, unsigned short *mirror_data_lport, uint8_t show_client_FPS_data); void raop_rtp_mirror_stop(raop_rtp_mirror_t *raop_rtp_mirror); void raop_rtp_mirror_destroy(raop_rtp_mirror_t *raop_rtp_mirror); diff --git a/lib/srp.c b/lib/srp.c index 6190de7e8..df206f973 100644 --- a/lib/srp.c +++ b/lib/srp.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -93,7 +94,6 @@ static struct NGHex global_Ng_constants[] = { "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", "2" }, -#if 0 /* begin removed section 2 */ { /* 3072 */ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B" "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485" @@ -108,6 +108,7 @@ static struct NGHex global_Ng_constants[] = { "D120A93AD2CAFFFFFFFFFFFFFFFF", "5" }, +#if 0 /* begin removed section 2 */ { /* 4096 */ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" @@ -205,7 +206,6 @@ static struct NGHex global_Ng_constants[] = { {0,0} /* null sentinel */ }; - static NGConstant * new_ng( SRP_NGType ng_type, const char * n_hex, const char * g_hex ) { NGConstant * ng = (NGConstant *) malloc( sizeof(NGConstant) ); @@ -295,7 +295,7 @@ static void handle_error(const char* location) { long error = ERR_get_error(); const char* error_str = ERR_error_string(error, NULL); fprintf(stderr, "SRP error at %s: %s\n", location, error_str); - exit(EXIT_FAILURE); + // exit(EXIT_FAILURE); } static void hash_destroy( HashCTX_t *ctx) { @@ -543,11 +543,11 @@ static void calculate_M( SRP_HashAlgorithm alg, NGConstant *ng, unsigned char * update_hash_n( alg, ctx, s ); update_hash_n( alg, ctx, A ); update_hash_n( alg, ctx, B ); -#ifdef APPLE_VARIANT /* Apple's SRP session key length is 2 x hash_len */ - hash_update( alg, ctx, K, 2 * hash_len ); -#else +// #ifdef APPLE_VARIANT /* Apple's SRP session key length is 2 x hash_len */ +// hash_update( alg, ctx, K, 2 * hash_len ); +// #else hash_update( alg, ctx, K, hash_len ); -#endif +// #endif hash_final( alg, ctx, dest, &dest_len ); hash_destroy ( ctx); @@ -562,11 +562,11 @@ static void calculate_H_AMK( SRP_HashAlgorithm alg, unsigned char *dest, const B hash_init( alg, ctx); update_hash_n( alg, ctx, A ); hash_update( alg, ctx, M, hash_length(alg) ); -#ifdef APPLE_VARIANT - hash_update( alg, ctx, K, 2 * hash_length(alg) ); -#else +// #ifdef APPLE_VARIANT +// hash_update( alg, ctx, K, 2 * hash_length(alg) ); +// #else hash_update( alg, ctx, K, hash_length(alg) ); -#endif +// #endif hash_final( alg, ctx, dest, &dest_len ); hash_destroy ( ctx); } @@ -639,8 +639,10 @@ void srp_create_salted_verification_key( SRP_HashAlgorithm alg, #ifdef APPLE_VARIANT //use a 16 byte salt BN_rand(s, 128, -1, 0); + // unsigned char salt[] = { 0x10, 0x24, 0x36, 0x7E, 0x07, 0xF4, 0xB4, 0xBC, 0xB1, 0x4A, 0x17, 0x6D, 0x80, 0xA5, 0xC7, 0x59 }; + // s = BN_bin2bn(salt, 16, NULL); #else - BN_rand(s, 32, -1, 0); + // BN_rand(s, 32, -1, 0); #endif x = calculate_x( alg, s, username, password, len_password ); @@ -845,21 +847,34 @@ struct SRPVerifier * srp_verifier_new( SRP_HashAlgorithm alg, SRP_NGType ng_typ goto cleanup_and_exit; } - /* S = (A *(v^u)) ^ b */ - BN_mod_exp(tmp1, v, u, ng->N, ctx); - BN_mul(tmp2, A, tmp1, ctx); - BN_mod_exp(S, tmp2, b, ng->N, ctx); + /* S = (A *(v^u)) ^ b */ + BN_mod_exp(tmp1, v, u, ng->N, ctx); + BN_mul(tmp2, A, tmp1, ctx); + BN_mod_exp(S, tmp2, b, ng->N, ctx); + + const unsigned char *bytes_S; + int len_S = BN_num_bytes(S); + bytes_S = (const unsigned char *)malloc( len_S ); + BN_bn2bin(S, (unsigned char *) bytes_S); -#ifdef APPLE_VARIANT - hash_session_key(alg, S, ver->session_key); -#else - hash_num(alg, S, ver->session_key); -#endif - calculate_M( alg, ng, ver->M, username, s, A, B, ver->session_key ); - calculate_H_AMK( alg, ver->H_AMK, A, ver->M, ver->session_key ); - *len_B = BN_num_bytes(B); - *bytes_B = (const unsigned char *)malloc( *len_B ); + +// #ifdef APPLE_VARIANT +// hash_session_key(alg, S, ver->session_key); +// #else + hash_num(alg, S, ver->session_key); +// #endif + calculate_M( alg, ng, ver->M, username, s, A, B, ver->session_key ); + calculate_H_AMK( alg, ver->H_AMK, A, ver->M, ver->session_key ); + + // printf("M (%d): ", 64); + // for (int i = 0; i < 64; i++) { + // printf("%02X ", ver->M[i]); + // } + // printf("\n\n\n"); + + *len_B = BN_num_bytes(B); + *bytes_B = (const unsigned char *)malloc( *len_B ); if( !((const unsigned char *)*bytes_B) ) { @@ -919,21 +934,21 @@ const char * srp_verifier_get_username( struct SRPVerifier * ver ) const unsigned char * srp_verifier_get_session_key( struct SRPVerifier * ver, int * key_length ) { if (key_length) -#ifdef APPLE_VARIANT - *key_length = 2 * hash_length( ver->hash_alg ); -#else +// #ifdef APPLE_VARIANT +// *key_length = 2 * hash_length( ver->hash_alg ); +// #else *key_length = hash_length( ver->hash_alg ); -#endif +// #endif return ver->session_key; } int srp_verifier_get_session_key_length( struct SRPVerifier * ver ) { -#ifdef APPLE_VARIANT - return 2 * hash_length( ver->hash_alg ); -#else +// #ifdef APPLE_VARIANT +// return 2 * hash_length( ver->hash_alg ); +// #else return hash_length( ver->hash_alg ); -#endif +// #endif } /* user_M must be exactly SHA512_DIGEST_LENGTH bytes in size */ @@ -948,6 +963,11 @@ void srp_verifier_verify_session( struct SRPVerifier * ver, const unsigned char *bytes_HAMK = NULL; } + +void get_error() { + handle_error("Here"); +} + /*******************************************************************************/ #if 0 /*begin removed section 4 */ struct SRPUser * srp_user_new( SRP_HashAlgorithm alg, SRP_NGType ng_type, const char * username, diff --git a/lib/srp.h b/lib/srp.h index 6af96a719..1e24dbed4 100644 --- a/lib/srp.h +++ b/lib/srp.h @@ -61,6 +61,8 @@ #define SRP_H #define APPLE_VARIANT +#include + struct SRPVerifier; #if 0 /*begin removed section 1*/ struct SRPUser; @@ -72,8 +74,8 @@ typedef enum SRP_NG_1536, #endif /* end removed section 2*/ SRP_NG_2048, -#if 0 /* begin removed section 3*/ SRP_NG_3072, +#if 0 /* begin removed section 3*/ SRP_NG_4096, SRP_NG_6144, SRP_NG_8192, @@ -190,6 +192,7 @@ void srp_verifier_verify_session( struct SRPVerifier * ver, const unsigned char * user_M, const unsigned char ** bytes_HAMK ); +void get_error(); /*******************************************************************************/ /* The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type diff --git a/renderers/video_renderer_gstreamer.c b/renderers/video_renderer_gstreamer.c index b8968c329..62aaa66ac 100644 --- a/renderers/video_renderer_gstreamer.c +++ b/renderers/video_renderer_gstreamer.c @@ -24,6 +24,8 @@ #include #include +#include + #define SECOND_IN_NSECS 1000000000UL #ifdef X_DISPLAY_FIX #include @@ -267,6 +269,11 @@ void video_renderer_render_buffer(unsigned char* data, int *data_len, int *nal_c * byte-aligned: the first byte of invalid data (decryption failed) is 0x01 */ if (data[0]) { logger_log(logger, LOGGER_ERR, "*** ERROR decryption of video packet failed "); + + printf("PACKET DUMP (%d): \n", data_len); + for (int i = 0; i < *data_len; i++) { + printf("%02X ", data[i]); + } } else { if (first_packet) { logger_log(logger, LOGGER_INFO, "Begin streaming to GStreamer video pipeline"); diff --git a/uxplay.cpp b/uxplay.cpp index ddf7af98a..3ec9d26d4 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -1375,11 +1375,11 @@ static int start_dnssd(std::vector hw_addr, std::string name) { dnssd_set_airplay_features(dnssd, 30, 1); // RAOP support: with this bit set, the AirTunes service is not required. dnssd_set_airplay_features(dnssd, 31, 0); // - for (int i = 32; i < 64; i++) { - dnssd_set_airplay_features(dnssd, i, 0); - } + // for (int i = 32; i < 64; i++) { + // dnssd_set_airplay_features(dnssd, i, 0); + // } - /* bits 32-63 are not used here: see https://emanualcozzi.net/docs/airplay2/features + /* bits 32-63 are not used here: see https://emanualcozzi.net/docs/airplay2/features */ dnssd_set_airplay_features(dnssd, 32, 0); // isCarPlay when ON,; Supports InitialVolume when OFF dnssd_set_airplay_features(dnssd, 33, 0); // Supports Air Play Video Play Queue dnssd_set_airplay_features(dnssd, 34, 0); // Supports Air Play from cloud (requires that bit 6 is ON) @@ -1400,7 +1400,7 @@ static int start_dnssd(std::vector hw_addr, std::string name) { dnssd_set_airplay_features(dnssd, 46, 0); // Supports HomeKit Pairing and Access Control dnssd_set_airplay_features(dnssd, 47, 0); // - dnssd_set_airplay_features(dnssd, 48, 0); // Supports CoreUtils Pairing and Encryption + dnssd_set_airplay_features(dnssd, 48, 1); // Supports CoreUtils Pairing and Encryption dnssd_set_airplay_features(dnssd, 49, 0); // dnssd_set_airplay_features(dnssd, 50, 0); // Metadata bit 3: "Now Playing" info sent by bplist not DAACP test dnssd_set_airplay_features(dnssd, 51, 0); // Supports Unified Pair Setup and MFi Authentication @@ -1417,7 +1417,7 @@ static int start_dnssd(std::vector hw_addr, std::string name) { dnssd_set_airplay_features(dnssd, 60, 0); // Supports Audo Media Data Control dnssd_set_airplay_features(dnssd, 61, 0); // Supports RFC2198 redundancy - */ + /* bit 27 of Features determines whether the AirPlay2 client-pairing protocol will be used (1) or not (0) */ dnssd_set_airplay_features(dnssd, 27, (int) setup_legacy_pairing); @@ -2161,11 +2161,12 @@ int main (int argc, char *argv[]) { stop_dnssd(); goto cleanup; } - if (register_dnssd()) { - stop_raop_server(); - stop_dnssd(); - goto cleanup; - } + register_dnssd(); + // if (register_dnssd()) { + // stop_raop_server(); + // stop_dnssd(); + // goto cleanup; + // } reconnect: compression_type = 0; close_window = new_window_closing_behavior;