/* * File: digest.c * * Copyright 2009 Justus Winter <4winter@informatik.uni-hamburg.de> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ #include #include "digest.h" #include "md5.h" #include "msg.h" #include "../dlib/dlib.h" static const char *ALGORITHM2STR[] = { NULL, "MD5", "MD5-sess" }; static const char *QOP2STR[] = { NULL, "auth", "auth-int" }; static const char hexchars[] = "0123456789abcdef"; static Dstr *md5hexdigest(const Dstr *data) { md5_state_t state; md5_byte_t digest[16]; Dstr *result = dStr_sized_new(33); int i; md5_init(&state); md5_append(&state, (const md5_byte_t *)data->str, data->len); md5_finish(&state, digest); for (i = 0; i < 16; i++) dStr_sprintfa(result, "%02x", digest[i]); return result; } /** * Returns a pointer to a newly allocated string containing a cnonce */ char *a_Digest_create_cnonce(void) { int i; char *result = dNew(char, 33); for (i = 0; i < 32; i++) result[i] = hexchars[rand() % 16]; result[32] = 0; return result; } /** * This portion only has to be calculated once. */ int a_Digest_compute_digest(AuthRealm_t *realm, const char *username, const char *passwd) { Dstr *a1; Dstr *digest; if (realm->algorithm == MD5 || realm->algorithm == ALGORITHMNOTSET) { /* A1 = unq(username-value) ":" unq(realm-value) ":" passwd */ a1 = dStr_new(NULL); dStr_sprintf(a1, "%s:%s:%s", username, realm->name, passwd); } else if (realm->algorithm == MD5SESS) { /* A1 = H( unq(username-value) ":" unq(realm-value) ** ":" passwd ) ** ":" unq(nonce-value) ":" unq(cnonce-value) */ Dstr *a0 = dStr_new(NULL); dStr_sprintf(a0, "%s:%s:%s", username, realm->name, passwd); Dstr *ha0 = md5hexdigest(a0); a1 = dStr_new(NULL); dStr_sprintf(a1, "%s:%s:%s", ha0, realm->nonce, realm->cnonce); dStr_free(a0, 1); dStr_free(ha0, 1); } else { MSG("a_Digest_create_auth: Unknown algorithm.\n"); return 0; } digest = md5hexdigest(a1); realm->authorization = digest->str; dStr_shred(a1); dStr_free(a1, 1); dStr_free(digest, 0); return 1; } /** * This portion is calculatd for each request. */ static Dstr *Digest_create_response(AuthRealm_t *realm, const char *method, const char *digest_uri, const Dstr *entity_body) { Dstr *ha2; Dstr *result; if (realm->qop == QOPNOTSET || realm->qop == AUTH) { /* A2 = Method ":" digest-uri-value */ Dstr *a2 = dStr_new(NULL); dStr_sprintf(a2, "%s:%s", method, digest_uri); ha2 = md5hexdigest(a2); dStr_free(a2, 1); } else if (realm->qop == AUTHINT) { /* A2 = Method ":" digest-uri-value ":" H(entity-body) */ Dstr *hentity = md5hexdigest(entity_body); Dstr *a2 = dStr_new(NULL); dStr_sprintf(a2, "%s:%s:%s", method, digest_uri, hentity->str); ha2 = md5hexdigest(a2); dStr_free(hentity, 1); dStr_free(a2, 1); } else { MSG("a_Digest_create_auth: Unknown qop value.\n"); return NULL; } result = dStr_new(NULL); if (realm->qop == AUTH || realm->qop == AUTHINT) { dStr_sprintf(result, "%s:%s:%08x:%s:%s:%s", realm->authorization, realm->nonce, realm->nonce_count, realm->cnonce, QOP2STR[realm->qop], ha2->str); } else { dStr_sprintf(result, "%s:%s:%s", realm->authorization, realm->nonce, ha2->str); } Dstr *request_digest = md5hexdigest(result); dStr_free(result, 1); dStr_free(ha2, 1); return request_digest; } static void Digest_Dstr_append_token_value(Dstr *str, int delimiter, const char *token, const char *value, int quoted) { char c; dStr_sprintfa(str, "%s%s=", (delimiter ? ", " : ""), token); if (quoted) { dStr_append_c(str, '"'); while ((c = *value++)) { if (c == '"') dStr_append_c(str, '\\'); dStr_append_c(str, c); } dStr_append_c(str, '"'); } else { dStr_append(str, value); } } /** * Construct Digest Authorization header. * * Field ordering: furaisanjin reports that his DVD recorder requires the * order that IE happens to use: "username, realm, nonce, uri, cnonce, nc, * algorithm, response, qop". It apparently doesn't use "opaque", so that's * been left where it already was. */ char *a_Digest_authorization_hdr(AuthRealm_t *realm, const DilloUrl *url, const char *digest_uri) { char *ret; Dstr *response, *result; const char *method = URL_FLAGS(url) & URL_Post ? "POST" : "GET"; realm->nonce_count++; response = Digest_create_response(realm, method, digest_uri, URL_DATA(url)); if (!response) return NULL; result = dStr_new("Authorization: Digest "); Digest_Dstr_append_token_value(result, 0, "username", realm->username, 1); Digest_Dstr_append_token_value(result, 1, "realm", realm->name, 1); Digest_Dstr_append_token_value(result, 1, "nonce", realm->nonce, 1); Digest_Dstr_append_token_value(result, 1, "uri", digest_uri, 1); if (realm->qop != QOPNOTSET) { Digest_Dstr_append_token_value(result, 1, "cnonce", realm->cnonce, 1); dStr_sprintfa(result, ", nc=%08x", realm->nonce_count); } if (realm->algorithm != ALGORITHMNOTSET) { Digest_Dstr_append_token_value(result, 1, "algorithm", ALGORITHM2STR[realm->algorithm], 0); } Digest_Dstr_append_token_value(result, 1, "response", response->str, 1); if (realm->opaque) Digest_Dstr_append_token_value(result, 1, "opaque", realm->opaque, 1); if (realm->qop != QOPNOTSET) Digest_Dstr_append_token_value(result, 1, "qop", QOP2STR[realm->qop], 1); dStr_sprintfa(result, "\r\n"); dStr_free(response, 1); ret = result->str; dStr_free(result, 0); return ret; }