Add support for password digests.
authorJuho Vähä-Herttua <juhovh@iki.fi>
Tue, 20 Mar 2012 19:23:00 +0000 (21:23 +0200)
committerJuho Vähä-Herttua <juhovh@iki.fi>
Wed, 16 May 2012 21:33:32 +0000 (00:33 +0300)
Makefile
src/include/dnssd.h
src/include/raop.h
src/lib/digest.c [new file with mode: 0644]
src/lib/digest.h [new file with mode: 0644]
src/lib/dnssd.c
src/lib/dnssd.m
src/lib/dnssdint.h
src/lib/raop.c
src/test/example.c

index 68e0132292a2bff986c3a99f46fcc171004ca121..b9e553c4c9f03b964554bd1b3afa2d77e27add74 100644 (file)
--- 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
index 4ad0e348bffc802950771fba2f173e999fbc32f8..19e6e643c42aa0a81fabff15c8e0e4a0c0b29524 100644 (file)
@@ -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);
index 51c8f0a607b280d328f23feb5b8a2b1ac11c3cf3..01d03eab8b56a177cd0e835ade69a0f1d15284e3 100644 (file)
@@ -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 (file)
index 0000000..132f27e
--- /dev/null
@@ -0,0 +1,153 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#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<MD5_SIZE*2; i++) {
+               int val = (i%2) ? md5buf[i/2]&0x0f : (md5buf[i/2]&0xf0)>>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(&current, ",")) != 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 (file)
index 0000000..f4b46e7
--- /dev/null
@@ -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
index 3f6d9cf283c748e77bcca4a0105a2a15ba8be31f..f2901e57e513a4d27ce5a066afbf5bac0de5a87d 100644 (file)
@@ -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);
index 5f87b1f68111f4507b0bc18de4bcb01234698b27..37ba32f30408e5f88fb3fd69984204214c507558 100644 (file)
@@ -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"];
index 803cc2bdd11e48b7c3e013473038293ee22c11cf..6b651171777a8baea2446ffb467789d28fac6db1 100644 (file)
@@ -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"
index b40ee2d367c079e990f42386fdaa7bd7516cadc7..0eaace1bbc2c2fde1d2ef2fbeae6c6a78b3fdd27 100644 (file)
@@ -20,6 +20,7 @@
 #include "raop.h"
 #include "raop_rtp.h"
 #include "rsakey.h"
+#include "digest.h"
 #include "httpd.h"
 #include "sdp.h"
 
 /* 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);
 }
 
index 41beb567b31c4c96d8d86316901f42c6364dd5ee..960e1258b70dd6c0367eceb1184551d252e7f34b 100644 (file)
@@ -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);