mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
Finally a fully working pair-pin-setup!
This commit is contained in:
@@ -388,7 +388,6 @@ int extract_evp_private_key(unsigned char *privkey, int keylen, EVP_PKEY *key) {
|
||||
if ('a' <= *data && *data <= 'f') val = 10 + *data - 'a';
|
||||
if ('A' <= *data && *data <= 'F') val = 10 + *data - 'A';
|
||||
if (val == 64) {
|
||||
//printf("[%c]\n", *data);
|
||||
data++;
|
||||
continue;
|
||||
}
|
||||
@@ -396,7 +395,6 @@ int extract_evp_private_key(unsigned char *privkey, int keylen, EVP_PKEY *key) {
|
||||
part = part% 2;
|
||||
switch (part) {
|
||||
case 1:
|
||||
//printf("%d %d [%c] %u\n", i, part, *data, val);
|
||||
part1 = val;
|
||||
data++;
|
||||
break;
|
||||
|
||||
140
lib/pairing.c
140
lib/pairing.c
@@ -12,17 +12,17 @@
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*==================================================================
|
||||
* modified by fduncanh 2021
|
||||
* modified by fduncanh 2021, 2023
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <openssl/sha.h> // for SHA512_DIGEST_LENGTH
|
||||
|
||||
#include "pairing.h"
|
||||
#include "crypto.h"
|
||||
#include "srp.h"
|
||||
|
||||
#define SALT_KEY "Pair-Verify-AES-Key"
|
||||
#define SALT_IV "Pair-Verify-AES-IV"
|
||||
@@ -31,6 +31,12 @@ struct pairing_s {
|
||||
ed25519_key_t *ed;
|
||||
};
|
||||
|
||||
typedef struct srp_user_s {
|
||||
char username[SRP_USERNAME_SIZE];
|
||||
unsigned char salt[SRP_SALT_SIZE];
|
||||
unsigned char verifier[SRP_VERIFIER_SIZE];
|
||||
} srp_user_t;
|
||||
|
||||
typedef enum {
|
||||
STATUS_INITIAL,
|
||||
STATUS_SETUP,
|
||||
@@ -47,6 +53,11 @@ struct pairing_session_s {
|
||||
x25519_key_t *ecdh_ours;
|
||||
x25519_key_t *ecdh_theirs;
|
||||
unsigned char ecdh_secret[X25519_KEY_SIZE];
|
||||
|
||||
/* srp items */
|
||||
srp_user_t *srp_user;
|
||||
unsigned char srp_session_key[SRP_SESSION_KEY_SIZE];
|
||||
|
||||
};
|
||||
|
||||
static int
|
||||
@@ -291,6 +302,131 @@ random_pin() {
|
||||
return (int) random_short;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (session->srp_user) {
|
||||
free (session->srp_user);
|
||||
}
|
||||
session->srp_user = (srp_user_t *) malloc(sizeof(srp_user_t));
|
||||
if (!session->srp_user) {
|
||||
return -2;
|
||||
}
|
||||
memset(session->srp_user, 0, sizeof(srp_user_t));
|
||||
strncpy(session->srp_user->username, device_id, strlen(device_id) + 1);
|
||||
|
||||
const unsigned char *srp_b = srp_private_key(pairing);
|
||||
unsigned char * srp_B;
|
||||
unsigned char * srp_s;
|
||||
unsigned char * srp_v;
|
||||
int len_b = ED25519_KEY_SIZE;
|
||||
int len_B;
|
||||
int len_s;
|
||||
int len_v;
|
||||
srp_create_salted_verification_key(SRP_SHA, SRP_NG, device_id,
|
||||
(const unsigned char *) pin, strlen (pin),
|
||||
(const unsigned char **) &srp_s, &len_s,
|
||||
(const unsigned char **) &srp_v, &len_v,
|
||||
NULL, NULL);
|
||||
if (len_s != SRP_SALT_SIZE || len_v != SRP_VERIFIER_SIZE) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
memcpy(session->srp_user->salt, srp_s, SRP_SALT_SIZE);
|
||||
memcpy(session->srp_user->verifier, srp_v, SRP_VERIFIER_SIZE);
|
||||
|
||||
*salt = (char *) session->srp_user->salt;
|
||||
*len_salt = len_s;
|
||||
|
||||
srp_create_server_ephemeral_key(SRP_SHA, SRP_NG,
|
||||
srp_v, len_v,
|
||||
srp_b, len_b,
|
||||
(const unsigned char **) &srp_B, &len_B,
|
||||
NULL, NULL, 1);
|
||||
|
||||
*pk = (char *) srp_B;
|
||||
*len_pk = len_B;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigned char *A,
|
||||
int len_A, unsigned char *proof, int client_proof_len, int proof_len) {
|
||||
int authenticated = 0;
|
||||
const unsigned char *B = NULL;
|
||||
const unsigned char *b = srp_private_key(pairing);
|
||||
int len_b = ED25519_KEY_SIZE;
|
||||
int len_B = 0;
|
||||
int len_K = 0;
|
||||
const unsigned char *session_key = NULL;
|
||||
const unsigned char *M2 = NULL;
|
||||
|
||||
struct SRPVerifier *verifier = srp_verifier_new(SRP_SHA, SRP_NG, (const char *) session->srp_user->username,
|
||||
(const unsigned char *) session->srp_user->salt, SRP_SALT_SIZE,
|
||||
(const unsigned char *) session->srp_user->verifier, SRP_VERIFIER_SIZE,
|
||||
A, len_A,
|
||||
b, len_b,
|
||||
&B, &len_B, NULL, NULL, 1);
|
||||
|
||||
srp_verifier_verify_session(verifier, proof, &M2);
|
||||
authenticated = srp_verifier_is_authenticated(verifier);
|
||||
if (authenticated == 0) {
|
||||
/* HTTP 470 should be sent to client if not verified.*/
|
||||
srp_verifier_delete(verifier);
|
||||
free (session->srp_user);
|
||||
session->srp_user = NULL;
|
||||
return -1;
|
||||
}
|
||||
session_key = srp_verifier_get_session_key(verifier, &len_K);
|
||||
if (len_K != SRP_SESSION_KEY_SIZE) {
|
||||
return -2;
|
||||
}
|
||||
memcpy(session->srp_session_key, session_key, len_K);
|
||||
memcpy(proof, M2, proof_len);
|
||||
srp_verifier_delete(verifier);
|
||||
return 0;
|
||||
}
|
||||
int
|
||||
srp_confirm_pair_setup(pairing_session_t *session, const unsigned char *pk,
|
||||
unsigned char *epk, unsigned char *auth_tag) {
|
||||
unsigned char aesKey[16], aesIV[16];
|
||||
unsigned char hash[SHA512_DIGEST_LENGTH];
|
||||
unsigned char pk_client[ED25519_KEY_SIZE];
|
||||
int pk_len_client, epk_len;
|
||||
|
||||
/* decrypt client epk to get client pk, authenticate with auth_tag*/
|
||||
|
||||
const char *salt = "Pair-Setup-AES-Key";
|
||||
sha_ctx_t *ctx = sha_init();
|
||||
sha_update(ctx, (const unsigned char *) salt, strlen(salt));
|
||||
sha_update(ctx, session->srp_session_key, SRP_SESSION_KEY_SIZE);
|
||||
sha_final(ctx, hash, NULL);
|
||||
sha_destroy(ctx);
|
||||
memcpy(aesKey, hash, 16);
|
||||
|
||||
salt = "Pair-Setup-AES-IV";
|
||||
ctx = sha_init();
|
||||
sha_update(ctx, (const unsigned char *) salt, strlen(salt));
|
||||
sha_update(ctx, session->srp_session_key, SRP_SESSION_KEY_SIZE);
|
||||
sha_final(ctx, hash, NULL);
|
||||
sha_destroy(ctx);
|
||||
memcpy(aesIV, hash, 16);
|
||||
aesIV[15]++;
|
||||
|
||||
pk_len_client = gcm_decrypt(epk, ED25519_KEY_SIZE, pk_client, aesKey, aesIV, auth_tag);
|
||||
if (pk_len_client <= 0) {
|
||||
/* authentication failed */
|
||||
return pk_len_client;
|
||||
}
|
||||
/* the previously undocumented necessary "nonce" */
|
||||
aesIV[15]++;
|
||||
epk_len = gcm_encrypt(pk, ED25519_KEY_SIZE, epk, aesKey, aesIV, auth_tag);
|
||||
return epk_len;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
|
||||
#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_SALT_SIZE 16
|
||||
#define SRP_PK_SIZE 256
|
||||
#define SRP_SHA SRP_SHA1
|
||||
#define SRP_NG SRP_NG_2048
|
||||
#define SRP_M2_SIZE 64
|
||||
#define GCM_AUTHTAG_SIZE 16
|
||||
#define SHA512_KEY_LENGTH 64
|
||||
|
||||
typedef struct pairing_s pairing_t;
|
||||
typedef struct pairing_session_s pairing_session_t;
|
||||
|
||||
@@ -41,4 +53,10 @@ void pairing_destroy(pairing_t *pairing);
|
||||
|
||||
int pairing_get_ecdh_secret_key(pairing_session_t *session, unsigned char ecdh_secret[X25519_KEY_SIZE]);
|
||||
|
||||
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);
|
||||
int srp_validate_proof(pairing_session_t *session, pairing_t *pairing, const unsigned char *A,
|
||||
int len_A, unsigned char *proof, int client_proof_len, int proof_len);
|
||||
int srp_confirm_pair_setup(pairing_session_t *session, const unsigned char *pk,
|
||||
unsigned char *epk, unsigned char *auth_tag);
|
||||
#endif
|
||||
|
||||
@@ -200,9 +200,161 @@ raop_handler_pairsetup_pin(raop_conn_t *conn,
|
||||
http_request_t *request, http_response_t *response,
|
||||
char **response_data, int *response_datalen) {
|
||||
|
||||
/* does nothing yet */
|
||||
const char *request_data;
|
||||
int request_datalen;
|
||||
bool data_is_plist = false;
|
||||
bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
|
||||
request_data = http_request_get_data(request, &request_datalen);
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "client requested pair-setup-pin, datalen = %d", request_datalen);
|
||||
if (request_datalen > 0) {
|
||||
char *header_str= NULL;
|
||||
http_request_get_header_string(request, &header_str);
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "request header: %s", header_str);
|
||||
data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
|
||||
free(header_str);
|
||||
}
|
||||
if (!data_is_plist) {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "did not receive expected plist from client, request_datalen = %d");
|
||||
goto authentication_failed;
|
||||
}
|
||||
|
||||
/* process the pair-setup-pin request */
|
||||
plist_t req_root_node = NULL;
|
||||
plist_from_bin(request_data, request_datalen, &req_root_node);
|
||||
plist_t req_method_node = plist_dict_get_item(req_root_node, "method");
|
||||
plist_t req_user_node = plist_dict_get_item(req_root_node, "user");
|
||||
plist_t req_pk_node = plist_dict_get_item(req_root_node, "pk");
|
||||
plist_t req_proof_node = plist_dict_get_item(req_root_node, "proof");
|
||||
plist_t req_epk_node = plist_dict_get_item(req_root_node, "epk");
|
||||
plist_t req_authtag_node = plist_dict_get_item(req_root_node, "authTag");
|
||||
|
||||
if (PLIST_IS_STRING(req_method_node) && PLIST_IS_STRING(req_user_node)) {
|
||||
/* this is the initial pair-setup-pin request */
|
||||
const char *salt;
|
||||
char pin[6];
|
||||
const char *pk;
|
||||
int len_pk, len_salt;
|
||||
char *method = NULL;
|
||||
char *user = NULL;
|
||||
plist_get_string_val(req_method_node, &method);
|
||||
if (strncmp(method, "pin", strlen (method))) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "error, required method is \"pin\", client requested \"%s\"", method);
|
||||
*response_data = NULL;
|
||||
response_datalen = 0;
|
||||
free (method);
|
||||
plist_free (req_root_node);
|
||||
return;
|
||||
}
|
||||
free (method);
|
||||
plist_get_string_val(req_user_node, &user);
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "pair-setup-pin: device_id = %s", user);
|
||||
snprintf(pin, 6, "%04u", conn->raop->pin);
|
||||
conn->raop->pin = 0;
|
||||
int ret = srp_new_user(conn->pairing, conn->raop->pairing, (const char *) user,
|
||||
(const char *) pin, &salt, &len_salt, &pk, &len_pk);
|
||||
free(user);
|
||||
plist_free(req_root_node);
|
||||
if (ret < 0) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "failed to create user, err = %d", ret);
|
||||
goto authentication_failed;
|
||||
}
|
||||
plist_t res_root_node = plist_new_dict();
|
||||
plist_t res_salt_node = plist_new_data(salt, len_salt);
|
||||
plist_t res_pk_node = plist_new_data(pk, len_pk);
|
||||
plist_dict_set_item(res_root_node, "pk", res_pk_node);
|
||||
plist_dict_set_item(res_root_node, "salt", res_salt_node);
|
||||
plist_to_bin(res_root_node, response_data, (uint32_t*) response_datalen);
|
||||
plist_free(res_root_node);
|
||||
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
|
||||
return;
|
||||
} else if (PLIST_IS_DATA(req_pk_node) && PLIST_IS_DATA(req_proof_node)) {
|
||||
/* this is the second part of pair-setup-pin request */
|
||||
char *client_pk = NULL;
|
||||
char *client_proof = NULL;
|
||||
unsigned char proof[64];
|
||||
memset(proof, 0, sizeof(proof));
|
||||
uint64_t client_pk_len;
|
||||
uint64_t client_proof_len;
|
||||
plist_get_data_val(req_pk_node, &client_pk, &client_pk_len);
|
||||
plist_get_data_val(req_proof_node, &client_proof, &client_proof_len);
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string((const unsigned char *) client_proof, client_proof_len, 20);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "client SRP6a proof <M> :\n%s", str);
|
||||
free (str);
|
||||
}
|
||||
memcpy(proof, client_proof, (int) client_proof_len);
|
||||
free (client_proof);
|
||||
int ret = srp_validate_proof(conn->pairing, conn->raop->pairing, (const unsigned char *) client_pk,
|
||||
(int) client_pk_len, proof, (int) client_proof_len, (int) sizeof(proof));
|
||||
free (client_pk);
|
||||
plist_free(req_root_node);
|
||||
if (ret < 0) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "Client Authentication Failure (client proof not validated)");
|
||||
goto authentication_failed;
|
||||
}
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string((const unsigned char *) proof, sizeof(proof), 20);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "server SRP6a proof <M1> :\n%s", str);
|
||||
free (str);
|
||||
}
|
||||
plist_t res_root_node = plist_new_dict();
|
||||
plist_t res_proof_node = plist_new_data((const char *) proof, 20);
|
||||
plist_dict_set_item(res_root_node, "proof", res_proof_node);
|
||||
plist_to_bin(res_root_node, response_data, (uint32_t*) response_datalen);
|
||||
plist_free(res_root_node);
|
||||
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
|
||||
return;
|
||||
} else if (PLIST_IS_DATA(req_epk_node) && PLIST_IS_DATA(req_authtag_node)) {
|
||||
/* this is the third part of pair-setup-pin request */
|
||||
char *client_epk = NULL;
|
||||
char *client_authtag = NULL;
|
||||
uint64_t client_epk_len;
|
||||
uint64_t client_authtag_len;
|
||||
unsigned char epk[ED25519_KEY_SIZE];
|
||||
unsigned char authtag[GCM_AUTHTAG_SIZE];
|
||||
unsigned char public_key[32];
|
||||
int ret;
|
||||
plist_get_data_val(req_epk_node, &client_epk, &client_epk_len);
|
||||
plist_get_data_val(req_authtag_node, &client_authtag, &client_authtag_len);
|
||||
|
||||
if (logger_debug) {
|
||||
char *str = utils_data_to_string((const unsigned char *) client_epk, client_epk_len, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "client_epk %d:\n%s\n", (int) client_epk_len, str);
|
||||
str = utils_data_to_string((const unsigned char *) client_authtag, client_authtag_len, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "client_authtag %d:\n%s\n", (int) client_authtag_len, str);
|
||||
free (str);
|
||||
}
|
||||
|
||||
memcpy(epk, client_epk, ED25519_KEY_SIZE);
|
||||
memcpy(authtag, client_authtag, GCM_AUTHTAG_SIZE);
|
||||
free (client_authtag);
|
||||
free (client_epk);
|
||||
plist_free(req_root_node);
|
||||
pairing_get_public_key(conn->raop->pairing, public_key);
|
||||
ret = srp_confirm_pair_setup(conn->pairing, public_key, epk, authtag);
|
||||
if (ret < 0) {
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "pair-pin-setup (step 3): client authentication failed\n");
|
||||
goto authentication_failed;
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "pair-pin-setup success\n");
|
||||
}
|
||||
pairing_session_set_setup_status(conn->pairing);
|
||||
plist_t res_root_node = plist_new_dict();
|
||||
plist_t res_epk_node = plist_new_data((const char *) epk, 32);
|
||||
plist_t res_authtag_node = plist_new_data((const char *) authtag, 16);
|
||||
plist_dict_set_item(res_root_node, "epk", res_epk_node);
|
||||
plist_dict_set_item(res_root_node, "authTag", res_authtag_node);
|
||||
plist_to_bin(res_root_node, response_data, (uint32_t*) response_datalen);
|
||||
plist_free(res_root_node);
|
||||
http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
|
||||
return;
|
||||
}
|
||||
authentication_failed:;
|
||||
http_response_destroy(response);
|
||||
response = http_response_init("RTSP/1.0", 470, "Client Authentication Failure");
|
||||
const char *cseq = http_request_get_header(request, "CSeq");
|
||||
http_response_add_header(response, "CSeq", cseq);
|
||||
http_response_add_header(response, "Server", "AirTunes/"GLOBAL_VERSION);
|
||||
*response_data = NULL;
|
||||
response_datalen = 0;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user