Add support for password digests.
[deb_shairplay.git] / src / lib / digest.c
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;
+}
+
+