#include "usuals.h" #include "md5.h" #include "httpauthd.h" #include "digest.h" #include /* A globally unique counter used to guarantee uniqueness of nonces */ static unsigned int g_digest_unique = 0; typedef struct internal_nonce { unsigned char hash[MD5_LEN]; time_t tm; unsigned int unique; } internal_nonce_t; void digest_makenonce(unsigned char* nonce, unsigned char* secret, unsigned char* old) { internal_nonce_t in; md5_ctx_t md5; ASSERT(nonce && secret); ASSERT(sizeof(internal_nonce_t) == DIGEST_NONCE_LEN); if(old) { internal_nonce_t* nold = (internal_nonce_t*)old; in.tm = nold->tm; in.unique = nold->unique; } else { in.tm = time(NULL); ha_lock(NULL); in.unique = g_digest_unique++; ha_unlock(NULL); } md5_init(&md5); md5_update(&md5, secret, DIGEST_SECRET_LEN); md5_update(&md5, &(in.tm), sizeof(in.tm)); md5_update(&md5, &(in.unique), sizeof(in.unique)); md5_final(in.hash, &md5); memcpy(nonce, &in, DIGEST_NONCE_LEN); } int digest_checknonce(unsigned char* nonce, unsigned char* secret, time_t* tm) { internal_nonce_t in; ASSERT(nonce && secret); ASSERT(sizeof(internal_nonce_t) == DIGEST_NONCE_LEN); digest_makenonce((unsigned char*)&in, secret, nonce); if(memcmp((unsigned char*)&in, nonce, DIGEST_SECRET_LEN) == 0) { if(tm) *tm = in.tm; return HA_OK; } return HA_FALSE; } digest_record_t* digest_makerec(unsigned char* nonce, const char* user) { digest_record_t* rec = (digest_record_t*)malloc(sizeof(*rec)); ASSERT(nonce && user); if(!rec) { ha_messagex(LOG_CRIT, "out of memory"); return NULL; } memset(rec, 0, sizeof(*rec)); memcpy(rec->nonce, nonce, DIGEST_NONCE_LEN); md5_string(rec->userhash, user); return rec; } const char* digest_challenge(ha_buffer_t* buf, const char* nonce_str, const char* realm, const char* domains, int stale) { ASSERT(buf && realm && nonce_str); ha_bufmcat(buf, HA_PREFIX_DIGEST, " realm=\"", realm, "\", nonce=\"", nonce_str, "\", qop=\"auth\", algorithm=\"MD5\"", NULL); if(domains) { ha_bufjoin(buf); ha_bufmcat(buf, ", domain=\"", domains, "\"", NULL); } if(stale) { ha_bufjoin(buf); ha_bufcat(buf, ", stale=true"); } return ha_bufdata(buf); } /* * Copyright 1999-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ int digest_parse(char* header, ha_buffer_t* buf, digest_header_t* rec, unsigned char* nonce) { char next; char* key; char* value; ASSERT(header && buf && rec); header = ha_bufcpy(buf, header); if(!header) return HA_CRITERROR; memset(rec, 0, sizeof(*rec)); while(header[0]) { /* find key */ while(header[0] && isspace(header[0])) header++; key = header; while(header[0] && header[0] != '=' && header[0] != ',' && !isspace(header[0])) header++; /* null terminate and move on */ next = header[0]; header[0] = '\0'; header++; if(!next) break; if(isspace(header[0])) { while(header[0] && isspace(header[0])) header++; next = header[0]; } /* find value */ if(next == '=') { while(header[0] && isspace(header[0])) header++; if(header[0] == '\"') /* quoted string */ { header++; value = header; while(header[0] && header[0] != '\"') header++; next = header[0]; header[0] = 0; header++; } else /* token */ { value = header; while(header[0] && header[0] != ',' && !isspace(header[0])) header++; next = header[0]; header[0] = 0; header++; } if(next != ',') { while(header[0] && header[0] != ',') header++; if(header[0]) header++; } if(!strcasecmp(key, "username")) rec->username = value; else if(!strcasecmp(key, "realm")) rec->realm = value; else if (!strcasecmp(key, "nonce")) rec->nonce = value; else if (!strcasecmp(key, "uri")) rec->uri = value; else if (!strcasecmp(key, "response")) rec->digest = value; else if (!strcasecmp(key, "algorithm")) rec->algorithm = value; else if (!strcasecmp(key, "cnonce")) rec->cnonce = value; else if (!strcasecmp(key, "opaque")) rec->opaque = value; else if (!strcasecmp(key, "qop")) rec->qop = value; else if (!strcasecmp(key, "nc")) rec->nc = value; } } if(nonce) { memset(nonce, 0, DIGEST_NONCE_LEN); if(rec->nonce) { size_t len = DIGEST_NONCE_LEN; void* d = ha_bufdechex(buf, rec->nonce, &len); if(d && len == DIGEST_NONCE_LEN) memcpy(nonce, d, DIGEST_NONCE_LEN); } } return HA_OK; } int digest_check(digest_header_t* dg, digest_record_t* rec, ha_options_t* opts, ha_buffer_t* buf, const char* method, const char* uri) { unsigned char hash[MD5_LEN]; md5_ctx_t md5; const char* digest; const char* t; ASSERT(opts && method && buf && dg && rec); /* TODO: Many of these should somehow communicate BAD REQ back to the client */ /* Check for digest */ if(!dg->digest || !dg->digest[0]) { ha_messagex(LOG_WARNING, "digest response missing digest"); return HA_BADREQ; } /* Username */ if(!dg->username || !dg->username[0]) { ha_messagex(LOG_WARNING, "digest response missing username"); return HA_BADREQ; } if(md5_strcmp(rec->userhash, dg->username) != 0) { ha_messagex(LOG_ERR, "digest response contains invalid username"); return HA_FALSE; } /* The realm */ if(!dg->realm) { ha_messagex(LOG_WARNING, "digest response contains missing realm"); return HA_BADREQ; } if(strcmp(dg->realm, opts->realm) != 0) { ha_messagex(LOG_ERR, "digest response contains invalid realm: %s", dg->realm); return HA_FALSE; } /* Components in the new RFC */ if(dg->qop) { /* * We only support 'auth' qop. We don't have access to the data * and wouldn't be able to support anything else. */ if(strcmp(dg->qop, "auth") != 0) { ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported qop: '%s'", dg->qop ? dg->qop : ""); return HA_BADREQ; } /* The cnonce */ if(!dg->cnonce || !dg->cnonce[0]) { ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); return HA_BADREQ; } if(!opts->digest_ignorenc) { /* The nonce count */ if(!dg->nc || !dg->nc[0]) { ha_messagex(LOG_WARNING, "digest response is missing nc value"); return HA_BADREQ; } /* Validate the nc */ else { char* e; long nc = strtol(dg->nc, &e, 16); if(*e) { ha_messagex(LOG_ERR, "digest response has invalid nc value: %s", dg->nc); return HA_BADREQ; } /* If we didn't a nc then save it away */ if(!*e && rec->nc == 0) rec->nc = nc; if(*e || nc != rec->nc) { ha_messagex(LOG_WARNING, "digest response has wrong nc value. " "possible replay attack: %s", dg->nc); return HA_FALSE; } } } } /* The algorithm */ if(dg->algorithm && strcasecmp(dg->algorithm, "MD5") != 0) { ha_messagex(LOG_WARNING, "digest response contains unknown or unsupported algorithm: '%s'", dg->algorithm ? dg->algorithm : ""); return HA_BADREQ; } /* Request URI */ if(!dg->uri) { ha_messagex(LOG_WARNING, "digest response is missing uri"); return HA_BADREQ; } if(!opts->digest_ignoreuri && strcmp(dg->uri, uri) != 0) { ha_uri_t d_uri; ha_uri_t s_uri; if(ha_uriparse(buf, dg->uri, &d_uri) < 0) { if(ha_buferr(buf)) return HA_CRITERROR; ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); return HA_BADREQ; } if(ha_uriparse(buf, uri, &s_uri) < 0) { if(ha_buferr(buf)) return HA_CRITERROR; ha_messagex(LOG_ERR, "server sent us an invalid uri"); return HA_BADREQ; } if(ha_uricmp(&d_uri, &s_uri) != 0) { ha_messagex(LOG_ERR, "digest response contains wrong uri: %s " "(should be %s)", dg->uri, uri); return HA_FALSE; } } /* * nonce: should have already been validated by the caller who * found this nice rec structure to pass us. * * opaque: We also don't use opaque. The caller should have validated it * if it's used there. */ /* * Now we validate the digest response */ /* Encode ha1 */ t = ha_bufenchex(buf, rec->ha1, MD5_LEN); if(t == NULL) return HA_CRITERROR; /* Encode ha2 */ md5_init(&md5); md5_update(&md5, method, strlen(method)); md5_update(&md5, ":", 1); md5_update(&md5, dg->uri, strlen(dg->uri)); md5_final(hash, &md5); ha_bufenchex(buf, hash, MD5_LEN); if(!ha_bufdata(buf)) return HA_CRITERROR; /* Old style digest (RFC 2069) */ if(!dg->qop) { md5_init(&md5); md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ md5_update(&md5, ":", 1); md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ md5_update(&md5, ":", 1); md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha1 */ md5_final(hash, &md5); } /* New style 'auth' digest (RFC 2617) */ else { md5_init(&md5); md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ md5_update(&md5, ":", 1); md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ md5_update(&md5, ":", 1); md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */ md5_update(&md5, ":", 1); md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ md5_update(&md5, ":", 1); md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */ md5_update(&md5, ":", 1); md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ md5_final(hash, &md5); } /* Encode the digest */ digest = ha_bufenchex(buf, hash, MD5_LEN); if(digest == NULL) return HA_CRITERROR; if(strcasecmp(dg->digest, digest) != 0) { ha_messagex(LOG_WARNING, "digest authentication failed for user: %s", dg->username); return HA_FALSE; } return HA_OK; } const char* digest_respond(ha_buffer_t* buf, digest_header_t* dg, digest_record_t* rec, unsigned char* next) { unsigned char hash[MD5_LEN]; md5_ctx_t md5; const char* nextnonce = NULL; const char* t; ASSERT(buf && dg && rec); /* This makes a new buffer */ ha_bufcpy(buf, ""); if(next) { nextnonce = ha_bufenc64(buf, next, DIGEST_NONCE_LEN); if(nextnonce == NULL) return NULL; } /* For older clients RFC 2069 */ if(!dg->qop) { if(nextnonce) ha_bufmcat(buf, "nextnonce=\"", nextnonce, "\"", NULL); return ha_bufdata(buf); } /* Otherwise we do the whole song and dance */ /* Encode ha1 */ t = ha_bufenchex(buf, rec->ha1, MD5_LEN); if(t == NULL) return NULL; /* Encode ha2 */ md5_init(&md5); md5_update(&md5, ":", 1); md5_update(&md5, dg->uri, strlen(dg->uri)); md5_final(hash, &md5); ha_bufenchex(buf, hash, MD5_LEN); if(!ha_bufdata(buf)) return NULL; /* New style 'auth' digest (RFC 2617) */ md5_init(&md5); md5_update(&md5, t, MD5_LEN * 2); /* ha1 */ md5_update(&md5, ":", 1); md5_update(&md5, dg->nonce, strlen(dg->nonce)); /* nonce */ md5_update(&md5, ":", 1); md5_update(&md5, dg->nc, strlen(dg->nc)); /* nc */ md5_update(&md5, ":", 1); md5_update(&md5, dg->cnonce, strlen(dg->cnonce)); /* cnonce */ md5_update(&md5, ":", 1); md5_update(&md5, dg->qop, strlen(dg->qop)); /* qop */ md5_update(&md5, ":", 1); md5_update(&md5, ha_bufdata(buf), MD5_LEN * 2); /* ha2 */ md5_final(hash, &md5); /* Encode the digest */ t = ha_bufenchex(buf, hash, MD5_LEN); if(t == NULL) return NULL; ha_bufmcat(buf, "rspauth=\"", t, "\"", ", qop=", dg->qop, ", nc=", dg->nc, ", cnonce=\"", dg->cnonce, "\"", NULL); if(nextnonce) { ha_bufjoin(buf); ha_bufmcat(buf, ", nextnonce=\"", nextnonce, "\"", NULL); } return ha_bufdata(buf); } void digest_makeha1(unsigned char* digest, const char* user, const char* realm, const char* password) { md5_ctx_t md5; ASSERT(digest && user && realm && password); md5_init(&md5); md5_update(&md5, user, strlen(user)); md5_update(&md5, ":", 1); md5_update(&md5, realm, strlen(realm)); md5_update(&md5, ":", 1); md5_update(&md5, password, strlen(password)); md5_final(digest, &md5); }