From e4169f77f892fefc66a97439d295d3e09ef3e2f0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Juho=20V=C3=A4h=C3=A4-Herttua?= Date: Tue, 20 Mar 2012 21:23:00 +0200 Subject: [PATCH] Add support for password digests. --- Makefile | 2 +- src/include/dnssd.h | 2 +- src/include/raop.h | 2 +- src/lib/digest.c | 153 ++++++++++++++++++++++++++++++++++++++++++++ src/lib/digest.h | 9 +++ src/lib/dnssd.c | 8 ++- src/lib/dnssd.m | 8 ++- src/lib/dnssdint.h | 1 - src/lib/raop.c | 62 +++++++++++++++++- src/test/example.c | 4 +- 10 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 src/lib/digest.c create mode 100644 src/lib/digest.h diff --git a/Makefile b/Makefile index 68e0132..b9e553c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CFLAGS:=-g -Wall -Isrc/include/ LDFLAGS:=-lm -LIB_OBJS=src/lib/alac/alac.o src/lib/crypto/aes.o src/lib/crypto/bigint.o src/lib/crypto/hmac.o src/lib/crypto/md5.o src/lib/crypto/rc4.o src/lib/crypto/sha1.o src/lib/sdp.o src/lib/raop_buffer.o src/lib/raop_rtp.o src/lib/http_response.o src/lib/http_request.o src/lib/http_parser.o src/lib/httpd.o src/lib/raop.o src/lib/rsakey.o src/lib/rsapem.o src/lib/dnssd.o src/lib/netutils.o src/lib/utils.o src/lib/base64.o src/lib/logger.o +LIB_OBJS=src/lib/alac/alac.o src/lib/crypto/aes.o src/lib/crypto/bigint.o src/lib/crypto/hmac.o src/lib/crypto/md5.o src/lib/crypto/rc4.o src/lib/crypto/sha1.o src/lib/sdp.o src/lib/raop_buffer.o src/lib/raop_rtp.o src/lib/digest.o src/lib/http_response.o src/lib/http_request.o src/lib/http_parser.o src/lib/httpd.o src/lib/raop.o src/lib/rsakey.o src/lib/rsapem.o src/lib/dnssd.o src/lib/netutils.o src/lib/utils.o src/lib/base64.o src/lib/logger.o all: example diff --git a/src/include/dnssd.h b/src/include/dnssd.h index 4ad0e34..19e6e64 100644 --- a/src/include/dnssd.h +++ b/src/include/dnssd.h @@ -21,7 +21,7 @@ typedef struct dnssd_s dnssd_t; DNSSD_API dnssd_t *dnssd_init(int *error); -DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen); +DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password); DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen); DNSSD_API void dnssd_unregister_raop(dnssd_t *dnssd); diff --git a/src/include/raop.h b/src/include/raop.h index 51c8f0a..01d03ea 100644 --- a/src/include/raop.h +++ b/src/include/raop.h @@ -26,7 +26,7 @@ typedef struct raop_callbacks_s raop_callbacks_t; RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks, const char *pemkey); RAOP_API raop_t *raop_init_from_keyfile(raop_callbacks_t *callbacks, const char *keyfile); -RAOP_API int raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen); +RAOP_API int raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password); RAOP_API void raop_stop(raop_t *raop); RAOP_API void raop_destroy(raop_t *raop); diff --git a/src/lib/digest.c b/src/lib/digest.c new file mode 100644 index 0000000..132f27e --- /dev/null +++ b/src/lib/digest.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include + +#include "compat.h" +#include "utils.h" +#include "crypto/crypto.h" + +void +digest_md5_to_hex(const unsigned char *md5buf, char *md5hex) +{ + int i; + for (i=0; i>4; + md5hex[i] = (val<10) ? '0'+val : 'a'+(val-10); + } +} + +void +digest_get_response(const char *username, const char *realm, + const char *password, const char *nonce, + const char *method, const char *uri, + char *response) +{ + MD5_CTX md5ctx; + unsigned char md5buf[MD5_SIZE]; + char md5hex[MD5_SIZE*2]; + + /* Calculate first inner MD5 hash */ + MD5_Init(&md5ctx); + MD5_Update(&md5ctx, (const unsigned char *)username, strlen(username)); + MD5_Update(&md5ctx, (const unsigned char *)":", 1); + MD5_Update(&md5ctx, (const unsigned char *)realm, strlen(realm)); + MD5_Update(&md5ctx, (const unsigned char *)":", 1); + MD5_Update(&md5ctx, (const unsigned char *)password, strlen(password)); + MD5_Final(md5buf, &md5ctx); + digest_md5_to_hex(md5buf, md5hex); + + /* Calculate second inner MD5 hash */ + MD5_Init(&md5ctx); + MD5_Update(&md5ctx, (const unsigned char *)method, strlen(method)); + MD5_Update(&md5ctx, (const unsigned char *)":", 1); + MD5_Update(&md5ctx, (const unsigned char *)uri, strlen(uri)); + MD5_Final(md5buf, &md5ctx); + + /* Calculate outer MD5 hash */ + MD5_Init(&md5ctx); + MD5_Update(&md5ctx, (const unsigned char *)md5hex, sizeof(md5hex)); + MD5_Update(&md5ctx, (const unsigned char *)":", 1); + MD5_Update(&md5ctx, (const unsigned char *)nonce, strlen(nonce)); + MD5_Update(&md5ctx, (const unsigned char *)":", 1); + digest_md5_to_hex(md5buf, md5hex); + MD5_Update(&md5ctx, (const unsigned char *)md5hex, sizeof(md5hex)); + MD5_Final(md5buf, &md5ctx); + + /* Store the final result to response */ + digest_md5_to_hex(md5buf, response); +} + +void +digest_generate_nonce(char *result, int resultlen) +{ + MD5_CTX md5ctx; + unsigned char md5buf[MD5_SIZE]; + char md5hex[MD5_SIZE*2]; + unsigned int time; + + SYSTEM_GET_TIME(time); + + MD5_Init(&md5ctx); + MD5_Update(&md5ctx, (unsigned char *)&time, sizeof(time)); + MD5_Final(md5buf, &md5ctx); + digest_md5_to_hex(md5buf, md5hex); + + strncpy(result, md5hex, resultlen-1); + result[resultlen-1] = '\0'; +} + +int +digest_is_valid(const char *our_realm, const char *password, + const char *our_nonce, const char *method, + const char *authorization) +{ + char *auth; + char *current; + char *value; + int success; + + /* Get values from authorization */ + char *username = NULL; + char *realm = NULL; + char *nonce = NULL; + char *uri = NULL; + char *response = NULL; + + /* Buffer for our response */ + char our_response[MD5_SIZE*2+1]; + + if (!authorization) { + return 0; + } + current = auth = strdup(authorization); + if (!auth) { + return 0; + } + + /* Check that the type is digest */ + if (strncmp("Digest", current, 6)) { + free(auth); + return 0; + } + current += 6; + + while ((value = utils_strsep(¤t, ",")) != NULL) { + char *first, *last; + + /* Find first and last characters */ + first = value; + last = value+strlen(value)-1; + + /* Trim spaces from the value */ + while (*first == ' ' && first < last) first++; + while (*last == ' ' && last > first) last--; + + /* Validate the last character */ + if (*last != '"') continue; + else *last = '\0'; + + /* Store value if it is relevant */ + if (!strncmp("username=\"", first, 10)) + username = first+10; + if (!strncmp("realm=\"", first, 7)) + realm = first+7; + if (!strncmp("nonce=\"", first, 7)) + nonce = first+7; + if (!strncmp("uri=\"", first, 5)) + uri = first+5; + if (!strncmp("response=\"", first, 10)) + response = first+10; + } + + /* Calculate our response */ + memset(our_response, 0, sizeof(our_response)); + digest_get_response(username, realm, password, nonce, + method, uri, our_response); + success = !strcmp(response, our_response); + free(auth); + + return success; +} + + diff --git a/src/lib/digest.h b/src/lib/digest.h new file mode 100644 index 0000000..f4b46e7 --- /dev/null +++ b/src/lib/digest.h @@ -0,0 +1,9 @@ +#ifndef DIGEST_H +#define DIGEST_H + +void digest_generate_nonce(char *result, int resultlen); +int digest_is_valid(const char *our_realm, const char *password, + const char *our_nonce, const char *method, + const char *authorization); + +#endif diff --git a/src/lib/dnssd.c b/src/lib/dnssd.c index 3f6d9cf..f2901e5 100644 --- a/src/lib/dnssd.c +++ b/src/lib/dnssd.c @@ -169,7 +169,7 @@ dnssd_destroy(dnssd_t *dnssd) } int -dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen) +dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password) { TXTRecordRef txtRecord; char servname[MAX_SERVNAME]; @@ -188,7 +188,11 @@ dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const dnssd->TXTRecordSetValue(&txtRecord, "da", strlen(RAOP_DA), RAOP_DA); dnssd->TXTRecordSetValue(&txtRecord, "sr", strlen(RAOP_SR), RAOP_SR); dnssd->TXTRecordSetValue(&txtRecord, "ss", strlen(RAOP_SS), RAOP_SS); - dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen(RAOP_PW), RAOP_PW); + if (password) { + dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen("true"), "true"); + } else { + dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen("false"), "false"); + } dnssd->TXTRecordSetValue(&txtRecord, "vn", strlen(RAOP_VN), RAOP_VN); dnssd->TXTRecordSetValue(&txtRecord, "tp", strlen(RAOP_TP), RAOP_TP); dnssd->TXTRecordSetValue(&txtRecord, "md", strlen(RAOP_MD), RAOP_MD); diff --git a/src/lib/dnssd.m b/src/lib/dnssd.m index 5f87b1f..37ba32f 100644 --- a/src/lib/dnssd.m +++ b/src/lib/dnssd.m @@ -50,7 +50,7 @@ dnssd_destroy(dnssd_t *dnssd) } int -dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen) +dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password) { char hwaddrstr[MAX_SERVNAME]; NSString *serviceString; @@ -80,7 +80,11 @@ dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const [txtDict setValue:[NSString stringWithUTF8String:RAOP_DA] forKey:@"da"]; [txtDict setValue:[NSString stringWithUTF8String:RAOP_SR] forKey:@"sr"]; [txtDict setValue:[NSString stringWithUTF8String:RAOP_SS] forKey:@"ss"]; - [txtDict setValue:[NSString stringWithUTF8String:RAOP_PW] forKey:@"pw"]; + if (password) { + [txtDict setValue:@"true" forKey:@"pw"]; + } else { + [txtDict setValue:@"false" forKey:@"pw"]; + } [txtDict setValue:[NSString stringWithUTF8String:RAOP_VN] forKey:@"vn"]; [txtDict setValue:[NSString stringWithUTF8String:RAOP_TP] forKey:@"tp"]; [txtDict setValue:[NSString stringWithUTF8String:RAOP_MD] forKey:@"md"]; diff --git a/src/lib/dnssdint.h b/src/lib/dnssdint.h index 803cc2b..6b65117 100644 --- a/src/lib/dnssdint.h +++ b/src/lib/dnssdint.h @@ -9,7 +9,6 @@ #define RAOP_DA "true" #define RAOP_SR "44100" #define RAOP_SS "16" -#define RAOP_PW "false" #define RAOP_VN "65537" #define RAOP_TP "TCP,UDP" #define RAOP_MD "0,1,2" diff --git a/src/lib/raop.c b/src/lib/raop.c index b40ee2d..0eaace1 100644 --- a/src/lib/raop.c +++ b/src/lib/raop.c @@ -20,6 +20,7 @@ #include "raop.h" #include "raop_rtp.h" #include "rsakey.h" +#include "digest.h" #include "httpd.h" #include "sdp.h" @@ -31,6 +32,12 @@ /* Actually 345 bytes for 2048-bit key */ #define MAX_SIGNATURE_LEN 512 +/* Let's just decide on some length */ +#define MAX_PASSWORD_LEN 64 + +/* MD5 as hex fits here */ +#define MAX_NONCE_LEN 33 + struct raop_s { /* Callbacks for audio */ raop_callbacks_t callbacks; @@ -45,6 +52,9 @@ struct raop_s { /* Hardware address information */ unsigned char hwaddr[MAX_HWADDR_LEN]; int hwaddrlen; + + /* Password information */ + char password[MAX_PASSWORD_LEN+1]; }; struct raop_conn_s { @@ -56,6 +66,8 @@ struct raop_conn_s { unsigned char *remote; int remotelen; + + char nonce[MAX_NONCE_LEN+1]; }; typedef struct raop_conn_s raop_conn_t; @@ -102,6 +114,8 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot conn->locallen = locallen; conn->remotelen = remotelen; + + digest_generate_nonce(conn->nonce, sizeof(conn->nonce)); return conn; } @@ -115,6 +129,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) const char *method; const char *cseq; const char *challenge; + int require_auth = 0; method = http_request_get_method(request); cseq = http_request_get_header(request, "CSeq"); @@ -123,6 +138,38 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) } res = http_response_init("RTSP/1.0", 200, "OK"); + if (strlen(raop->password)) { + const char *authorization; + + authorization = http_request_get_header(request, "Authorization"); + if (authorization) { + logger_log(&conn->raop->logger, LOGGER_DEBUG, "Authorization: %s\n", authorization); + } + if (!digest_is_valid("AppleTV", raop->password, conn->nonce, method, authorization)) { + char *authstr; + int authstrlen; + + /* Allocate the authenticate string */ + authstrlen = sizeof("Digest realm=\"AppleTV\", nonce=\"\"") + sizeof(conn->nonce) + 1; + authstr = malloc(authstrlen); + + /* Concatenate the authenticate string */ + memset(authstr, 0, authstrlen); + strcat(authstr, "Digest realm=\"AppleTV\", nonce=\""); + strcat(authstr, conn->nonce); + strcat(authstr, "\""); + + /* Construct a new response */ + require_auth = 1; + http_response_destroy(res); + res = http_response_init("RTSP/1.0", 401, "Unauthorized"); + http_response_add_header(res, "WWW-Authenticate", authstr); + free(authstr); + } else { + logger_log(&conn->raop->logger, LOGGER_DEBUG, "AUTHENTICATION SUCCESS!\n"); + } + } + http_response_add_header(res, "CSeq", cseq); http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog"); @@ -138,7 +185,10 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) logger_log(&conn->raop->logger, LOGGER_DEBUG, "Got challenge: %s\n", challenge); logger_log(&conn->raop->logger, LOGGER_DEBUG, "Got response: %s\n", signature); } - if (!strcmp(method, "OPTIONS")) { + + if (require_auth) { + /* Do nothing in case of authentication request */ + } else if (!strcmp(method, "OPTIONS")) { http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER"); } else if (!strcmp(method, "ANNOUNCE")) { const char *data; @@ -378,7 +428,7 @@ raop_destroy(raop_t *raop) } int -raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen) +raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password) { assert(raop); assert(port); @@ -389,10 +439,18 @@ raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen return -1; } + /* Validate password */ + if (strlen(password) > MAX_PASSWORD_LEN) { + return -1; + } + /* Copy hwaddr to the raop structure */ memcpy(raop->hwaddr, hwaddr, hwaddrlen); raop->hwaddrlen = hwaddrlen; + /* Copy password to the raop structure */ + strncpy(raop->password, password, MAX_PASSWORD_LEN); + return httpd_start(raop->httpd, port); } diff --git a/src/test/example.c b/src/test/example.c index 41beb56..960e125 100644 --- a/src/test/example.c +++ b/src/test/example.c @@ -61,10 +61,10 @@ main(int argc, char *argv[]) raop_cbs.audio_destroy = audio_destroy; raop = raop_init_from_keyfile(&raop_cbs, "airport.key"); - raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr)); + raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr), "test"); dnssd = dnssd_init(NULL); - dnssd_register_raop(dnssd, name, raop_port, hwaddr, sizeof(hwaddr)); + dnssd_register_raop(dnssd, name, raop_port, hwaddr, sizeof(hwaddr), 1); #ifndef WIN32 sleep(100); -- 2.34.1