add suppport for user access control by -pw password (HTML digest)

This commit is contained in:
F. Duncanh
2025-04-22 17:13:13 -04:00
parent 2f809eeadd
commit be4fb423c4
17 changed files with 566 additions and 81 deletions

View File

@@ -507,6 +507,8 @@ void ed25519_key_destroy(ed25519_key_t *key) {
}
}
// SHA 512
struct sha_ctx_s {
@@ -540,7 +542,6 @@ void sha_final(sha_ctx_t *ctx, uint8_t *out, unsigned int *len) {
void sha_reset(sha_ctx_t *ctx) {
if (!EVP_MD_CTX_reset(ctx->digest_ctx) ||
!EVP_DigestInit_ex(ctx->digest_ctx, EVP_sha512(), NULL)) {
handle_error(__func__);
}
}
@@ -552,6 +553,63 @@ void sha_destroy(sha_ctx_t *ctx) {
}
}
//MD5
struct md5_ctx_s {
EVP_MD_CTX *digest_ctx;
};
md5_ctx_t *md5_init() {
md5_ctx_t *ctx = malloc(sizeof(md5_ctx_t));
assert(ctx != NULL);
ctx->digest_ctx = EVP_MD_CTX_new();
assert(ctx->digest_ctx != NULL);
if (!EVP_DigestInit_ex(ctx->digest_ctx, EVP_md5(), NULL)) {
handle_error(__func__);
}
return ctx;
}
void md5_update(md5_ctx_t *ctx, const uint8_t *in, int len) {
if (!EVP_DigestUpdate(ctx->digest_ctx, in, len)) {
handle_error(__func__);
}
}
void md5_final(md5_ctx_t *ctx, uint8_t *out, unsigned int *len) {
if (!EVP_DigestFinal_ex(ctx->digest_ctx, out, len)) {
handle_error(__func__);
}
}
void md5_reset(md5_ctx_t *ctx) {
if (!EVP_MD_CTX_reset(ctx->digest_ctx) ||
!EVP_DigestInit_ex(ctx->digest_ctx, EVP_md5(), NULL)) {
handle_error(__func__);
}
}
void md5_destroy(md5_ctx_t *ctx) {
if (ctx) {
EVP_MD_CTX_free(ctx->digest_ctx);
free(ctx);
}
}
#define MD5_DIGEST_LENGTH 16
char *get_md5(char *string) {
unsigned char hash[MD5_DIGEST_LENGTH];
md5_ctx_t *ctx = NULL;
ctx = md5_init();
md5_update(ctx, (const unsigned char *) string, strlen(string));
md5_final(ctx, hash, NULL);
md5_destroy(ctx);
ctx = NULL;
char *result_str = utils_hex_to_string(hash, MD5_DIGEST_LENGTH);
return result_str; //must free result_str after use
}
int get_random_bytes(unsigned char *buf, int num) {
return RAND_bytes(buf, num);
}

View File

@@ -109,6 +109,15 @@ 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);
//MD5
typedef struct md5_ctx_s md5_ctx_t;
md5_ctx_t *md5_init();
void md5_update(md5_ctx_t *ctx, const uint8_t *in, int len);
void md5_final(md5_ctx_t *ctx, uint8_t *out, unsigned int *len);
void md5_reset(md5_ctx_t *ctx);
void md5_destroy(md5_ctx_t *ctx);
char *get_md5(char *string);
#ifdef __cplusplus
}
#endif

View File

@@ -151,17 +151,21 @@ struct dnssd_s {
uint32_t features1;
uint32_t features2;
unsigned char require_pw;
unsigned char pin_pw;
};
dnssd_t *
dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error, int require_pw)
dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error, unsigned char pin_pw)
{
dnssd_t *dnssd;
char *end;
unsigned long features;
/* pin_pw = 0: no pin or password
1: use onscreen pin for client access control
2: require password for client accress control.
*/
if (error) *error = DNSSD_ERROR_NOERROR;
@@ -171,7 +175,7 @@ dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len,
return NULL;
}
dnssd->require_pw = (unsigned char) require_pw;
dnssd->pin_pw = pin_pw;
features = strtoul(FEATURES_1, &end, 16);
if (!end || (features & 0xFFFFFFFF) != features) {
@@ -302,10 +306,19 @@ dnssd_register_raop(dnssd_t *dnssd, unsigned short port)
dnssd->TXTRecordSetValue(&dnssd->raop_record, "am", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "md", strlen(RAOP_MD), RAOP_MD);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "rhd", strlen(RAOP_RHD), RAOP_RHD);
if (dnssd->require_pw) {
switch (dnssd->pin_pw) {
case 2:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true");
} else {
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("false"), "false");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", 4, "0x84");
break;
case 1:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", 3, "0x8c");
break;
default:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "false");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", strlen(RAOP_SF), RAOP_SF);
break;
}
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sr", strlen(RAOP_SR), RAOP_SR);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ss", strlen(RAOP_SS), RAOP_SS);
@@ -361,18 +374,26 @@ dnssd_register_airplay(dnssd_t *dnssd, unsigned short port)
return -1;
}
// flags is a string representing a 20-bit flag (up to 3 hex digits)
dnssd->TXTRecordCreate(&dnssd->airplay_record, 0, NULL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "deviceid", strlen(device_id), device_id);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "features", strlen(features), features);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", strlen(AIRPLAY_FLAGS), AIRPLAY_FLAGS);
switch (dnssd->pin_pw) {
case 1: // display onscreen pin
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
case 2: // require password
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
default:
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("false"), "false");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
}
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "model", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pk", strlen(dnssd->pk), dnssd->pk);
if (dnssd->require_pw) {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
} else {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("false"), "false");
}
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pi", strlen(AIRPLAY_PI), AIRPLAY_PI);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "srcvers", strlen(AIRPLAY_SRCVERS), AIRPLAY_SRCVERS);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "vv", strlen(AIRPLAY_VV), AIRPLAY_VV);

View File

@@ -35,7 +35,7 @@ extern "C" {
typedef struct dnssd_s dnssd_t;
DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, int require_pw);
DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, unsigned char pin_pw);
DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, unsigned short port);
DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, unsigned short port);

View File

@@ -44,7 +44,7 @@
#define RAOP_VN "65537"
#define AIRPLAY_SRCVERS GLOBAL_VERSION /*defined in global.h */
#define AIRPLAY_FLAGS "0x4"
#define AIRPLAY_FLAGS "0x84"
#define AIRPLAY_VV "2"
#define AIRPLAY_PI "2e388006-13ba-4041-9a67-25dd4a43d536"

View File

@@ -16,6 +16,7 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
@@ -195,6 +196,168 @@ pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_ke
return 0;
}
int
pairing_session_make_nonce(pairing_session_t *session, uint64_t *local_time, const char *client_data, unsigned char *nonce, int len) {
unsigned char hash[SHA512_DIGEST_LENGTH];
if (len > sizeof(hash)) {
return -1;
}
if (!client_data || !local_time || !session || !nonce || len <= 0) {
return -2;
}
sha_ctx_t *ctx = sha_init();
sha_update(ctx, (const unsigned char *) local_time, sizeof(uint64_t));
sha_update(ctx, (const unsigned char *) client_data, strlen(client_data));
sha_update(ctx, (const unsigned char *) session->ed_ours, ED25519_KEY_SIZE);
sha_final(ctx, hash, NULL);
sha_destroy(ctx);
memcpy(nonce, hash, len);
return 0;
}
static
char *get_token(char **cursor, char *token_name, char start_char, char end_char) {
char *ptr = *cursor;
ptr = strstr(ptr, token_name);
if (!ptr) {
return NULL;
}
ptr += strlen(token_name);
ptr = strchr(ptr, start_char);
if (!ptr) {
return NULL;
}
char *token = ++ptr;
ptr = strchr(ptr, end_char);
if (!ptr) {
return NULL;
}
*(ptr++) = '\0';
*cursor = ptr;
return token;
}
//#define test_digest
bool
pairing_digest_verify(const char *method, const char * authorization, const char *password) {
/* RFC 2617 HTTP md5 Digest password authentication */
char *sentence = (char *) calloc(strlen(authorization) + 1, sizeof(char));
strncpy(sentence, authorization, strlen(authorization));
char *username = NULL;
char *realm = NULL;
char *nonce = NULL;
char *uri = NULL;
char *qop = NULL;
char *nc = NULL;
char *cnonce = NULL;
char *response = NULL;
char *cursor = sentence;
const char *pwd = password;
const char *mthd = method;
char *raw;
int len;
bool authenticated;
#ifdef test_digest
char testauth[] = "Digest username=\"Mufasa\","
"realm=\"testrealm@host.com\","
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
"uri=\"/dir/index.html\","
"qop=auth,"
"nc=00000001,"
"cnonce=\"0a4f113b\","
"response=\"6629fae49393a05397450978507c4ef1\","
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""
;
pwd = "Circle Of Life";
mthd = "GET";
cursor = testauth;
char HA1[] = "939e7578ed9e3c518a452acee763bce9";
char HA2[] = "39aff3a2bab6126f332b942af96d3366";
#endif
username = get_token(&cursor, "username", '\"', '\"');
realm = get_token(&cursor, "realm", '\"', '\"');
nonce = get_token(&cursor,"nonce", '\"', '\"');
uri = get_token(&cursor,"uri", '\"', '\"');
qop = get_token(&cursor, "qop", '=', ',');
if (qop) {
nc = get_token(&cursor, "nc", '=', ',');
cnonce = get_token(&cursor, "cnonce", '\"', '\"');
}
response = get_token(&cursor, "response", '\"', '\"');
#ifdef test_digest
printf("username: [%s] realm: [%s]\n", username, realm);
printf("nonce: [%s]\n", nonce);
printf("method: [%s]\n", mthd);
printf("uri: [%s]\n", uri);
if (qop) {
printf("qop: [%s], nc=[%s], cnonce: [%s]\n", qop, nc, cnonce);
}
printf("response: [%s]\n", response);
#endif
/* H1 = H(username : realm : password ) */
len = strlen(username) + strlen(realm) + strlen(pwd) + 3;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s", username, realm, pwd);
char *hash1 = get_md5(raw);
free (raw);
#ifdef test_digest
printf("hash1: should be %s, was: %s\n ", HA1, hash1_str);
#endif
/* H2 = H(method : uri) */
len = strlen(mthd) + strlen(uri) + 2;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s", mthd, uri);
char *hash2 = get_md5(raw);
free (raw);
#ifdef test_digest
printf("hash2: should be %s, was: %s\n", HA2, hash2_str);
#endif
/* result = H(H1 : nonce (or nonce:nc:cnonce:qop) : H2) */
len = strlen(hash1) + strlen(nonce) + strlen(hash2) + 3;
if (qop) {
len += strlen(nc) + strlen(cnonce) + strlen(qop) + 3;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s:%s:%s:%s", hash1, nonce, nc, cnonce, qop, hash2);
} else {
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s", hash1, nonce, hash2);
}
free (hash1);
free (hash2);
char *result = get_md5(raw);
free (raw);
authenticated = (strcmp(result,response) ? false : true);
#ifdef test_digest
printf("result: should be %s, was: %s, authenticated is %s\n", response, result, (authenticated ? "true" : "false"));
#endif
free (result);
free(sentence);
#ifdef test_digest
exit(0);
#endif
return authenticated;
}
int
pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE])
{

View File

@@ -62,4 +62,7 @@ int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsig
unsigned char *auth_tag);
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);
int pairing_session_make_nonce(pairing_session_t *session, uint64_t *local_time, const char *client_data, unsigned char *nonce, int len);
bool pairing_digest_verify(const char *method, const char * authorization, const char *password);
#endif

View File

@@ -66,17 +66,21 @@ struct raop_s {
int audio_delay_micros;
/* for temporary storage of pin during pair-pin start */
unsigned short pin;
bool use_pin;
unsigned short pin;
bool use_pin;
/* public key as string */
char pk_str[2*ED25519_KEY_SIZE + 1];
char pk_str[2*ED25519_KEY_SIZE + 1];
/* place to store media_data_store */
airplay_video_t *airplay_video;
airplay_video_t *airplay_video;
/* activate support for HLS live streaming */
bool hls_support;
bool hls_support;
/* used in digest authentication */
char *nonce;
char * random_pw;
};
struct raop_conn_s {
@@ -99,7 +103,7 @@ struct raop_conn_s {
connection_type_t connection_type;
char *client_session_id;
bool authenticated;
bool have_active_remote;
};
typedef struct raop_conn_s raop_conn_t;
@@ -159,6 +163,7 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
conn->client_session_id = NULL;
conn->airplay_video = NULL;
conn->authenticated = false;
conn->have_active_remote = false;
@@ -579,6 +584,7 @@ raop_init(raop_callbacks_t *callbacks) {
raop->hls_support = false;
raop->nonce = NULL;
return raop;
}
@@ -602,7 +608,7 @@ raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile)
#else
unsigned char public_key[ED25519_KEY_SIZE];
pairing_get_public_key(pairing, public_key);
char *pk_str = utils_pk_to_string(public_key, ED25519_KEY_SIZE);
char *pk_str = utils_hex_to_string(public_key, ED25519_KEY_SIZE);
strncpy(raop->pk_str, (const char *) pk_str, 2*ED25519_KEY_SIZE);
free(pk_str);
#endif
@@ -638,6 +644,13 @@ raop_destroy(raop_t *raop) {
pairing_destroy(raop->pairing);
httpd_destroy(raop->httpd);
logger_destroy(raop->logger);
if (raop->nonce) {
free(raop->nonce);
}
if (raop->random_pw) {
free(raop->random_pw);
}
free(raop);
/* Cleanup the network */

View File

@@ -90,6 +90,7 @@ struct raop_callbacks_s {
void (*display_pin) (void *cls, char * pin);
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);
const char* (*passwd) (void *cls, int *len);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
int (*video_set_codec)(void *cls, video_codec_t codec);
/* for HLS video player controls */

View File

@@ -25,6 +25,7 @@
#include <plist/plist.h>
#define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000
typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *,
http_response_t *, char **, int *);
@@ -36,6 +37,33 @@ raop_handler_info(raop_conn_t *conn,
{
assert(conn->raop->dnssd);
#if 0
/* initial GET/info request sends plist with string "txtAirPlay" */
bool txtAirPlay = false;
const char* content_type = NULL;
content_type = http_request_get_header(request, "Content-Type");
if (content_type && strstr(content_type, "application/x-apple-binary-plist")) {
char *qualifier_string = NULL;
const char *data = NULL;
int data_len = 0;
data = http_request_get_data(request, &data_len);
//parsing bplist
plist_t req_root_node = NULL;
plist_from_bin(data, data_len, &req_root_node);
plist_t req_qualifier_node = plist_dict_get_item(req_root_node, "qualifier");
if (PLIST_IS_ARRAY(req_qualifier_node)) {
plist_t req_string_node = plist_array_get_item(req_qualifier_node, 0);
plist_get_string_val(req_string_node, &qualifier_string);
}
if (qualifier_string && !strcmp(qualifier_string, "txtAirPlay")) {
printf("qualifier: %s\n", qualifier_string);
txtAirPlay = true;
}
if (qualifier_string) {
free(qualifier_string);
}
}
#endif
plist_t res_node = plist_new_dict();
/* deviceID is the physical hardware address, and will not change */
@@ -548,13 +576,88 @@ raop_handler_setup(raop_conn_t *conn,
if (PLIST_IS_DATA(req_eiv_node) && PLIST_IS_DATA(req_ekey_node)) {
// The first SETUP call that initializes keys and timing
unsigned char aesiv[16] = { 0 };
unsigned char aeskey[16] = { 0 };
unsigned char eaeskey[72] = { 0 };
unsigned char aesiv[16] = { 0 };
unsigned char aeskey[16] = { 0 };
unsigned char eaeskey[72] = { 0 };
logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1");
// First setup
/* RFC2617 Digest authentication (md5 hash) of uxplay client-access password, if set */
if (!conn->authenticated && conn->raop->callbacks.passwd) {
int len;
const char *password = conn->raop->callbacks.passwd(conn->raop->callbacks.cls, &len);
// len = -1 means use a random password for this connection
if (len == -1 && !conn->raop->random_pw) {
// get and store 4 random digits
int pin_4 = random_pin();
if (pin_4 < 0) {
logger_log(conn->raop->logger, LOGGER_ERR, "Failed to generate random pin");
}
char pin[6] = {'\0'};
snprintf(pin, 5, "%04u", pin_4 % 10000);
printf("*** set new pin = [%s]\n", pin);
conn->raop->random_pw = strndup((const char *) pin, 6);
printf("*** stored new pin = [%s]\n", conn->raop->random_pw);
}
if (len == -1 && conn->raop->callbacks.display_pin) {
char *pin = conn->raop->random_pw;
assert(pin);
conn->raop->callbacks.display_pin(conn->raop->callbacks.cls, pin);
logger_log(conn->raop->logger, LOGGER_INFO, "*** CLIENT MUST NOW ENTER PIN = \"%s\" AS AIRPLAY PASSWORD", pin);
password = (const char *) pin;
}
if (len && !conn->authenticated) {
char nonce_string[33] = { '\0' };
//bool stale = false; //not implemented
const char *authorization = NULL;
authorization = http_request_get_header(request, "Authorization");
if (authorization) {
char *ptr = strstr(authorization, "nonce=\"") + strlen("nonce=\"");
strncpy(nonce_string, ptr, 32);
const char *method = http_request_get_method(request);
conn->authenticated = pairing_digest_verify(method, authorization, password);
if (conn->authenticated) {
printf("initial authenticatication OK\n");
conn->authenticated = conn->authenticated && !strcmp(nonce_string, conn->raop->nonce);
if (!conn->authenticated) {
logger_log(conn->raop->logger, LOGGER_INFO, "authentication rejected (nonce mismatch) %s %s",
nonce_string, conn->raop->nonce);
}
}
if (conn->authenticated && conn->raop->random_pw) {
printf("*********free random_pw\n");
free (conn->raop->random_pw);
conn->raop->random_pw = NULL;
}
if (conn->raop->nonce) {
free(conn->raop->nonce);
conn->raop->nonce = NULL;
}
logger_log(conn->raop->logger, LOGGER_INFO, "Client authentication %s", (conn->authenticated ? "success" : "failure"));
}
if (!conn->authenticated) {
/* create a nonce */
const char *url = http_request_get_url(request);
unsigned char nonce[16] = { '\0' };
int len = 16;
uint64_t now = raop_ntp_get_local_time();
assert (!pairing_session_make_nonce(conn->session, &now, url, nonce, len));
if (conn->raop->nonce) {
free(conn->raop->nonce);
}
conn->raop->nonce = utils_hex_to_string(nonce, len);
char response_text[80] = "Digest realm=\"raop\", nonce=\"";
strncat(response_text, conn->raop->nonce, strlen(conn->raop->nonce));
strncat(response_text, "\"", 1);
http_response_init(response, "RTSP/1.0", 401, "Unauthorized");
http_response_add_header(response, "WWW-Authenticate", response_text);
return;
}
}
}
char* eiv = NULL;
uint64_t eiv_len = 0;
@@ -1047,7 +1150,7 @@ raop_handler_teardown(raop_conn_t *conn,
uint64_t val;
int count = plist_array_get_size(req_streams_node);
for (int i = 0; i < count; i++) {
plist_t req_stream_node = plist_array_get_item(req_streams_node,0);
plist_t req_stream_node = plist_array_get_item(req_streams_node,i);
plist_t req_stream_type_node = plist_dict_get_item(req_stream_node, "type");
plist_get_uint_val(req_stream_type_node, &val);
if (val == 96) {

View File

@@ -186,14 +186,14 @@ char *utils_parse_hex(const char *str, int str_len, int *data_len) {
return data;
}
char *utils_pk_to_string(const unsigned char *pk, int pk_len) {
char *pk_str = (char *) malloc(2*pk_len + 1);
char* pos = pk_str;
for (int i = 0; i < pk_len; i++) {
snprintf(pos, 3, "%2.2x", *(pk + i));
char *utils_hex_to_string(const unsigned char *hex, int hex_len) {
char *hex_str = (char *) malloc(2*hex_len + 1);
char* pos = hex_str;
for (int i = 0; i < hex_len; i++) {
snprintf(pos, 3, "%2.2x", *(hex + i));
pos +=2;
}
return pk_str;
return hex_str;
}
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line) {

View File

@@ -25,7 +25,7 @@ int utils_read_file(char **dst, const char *pemstr);
int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen);
int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen);
char *utils_parse_hex(const char *str, int str_len, int *data_len);
char *utils_pk_to_string(const unsigned char *pk, int pk_len);
char *utils_hex_to_string(const unsigned char *hex, int hex_len);
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line);
char *utils_data_to_text(const char *data, int datalen);
void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);