From cbbe71752d7f9c6204ab0f16600fe7f10490f203 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 24 Apr 2004 22:38:50 +0000 Subject: Completed implementation of ldap/ntlm/simple handlers --- daemon/digest.c | 533 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100644 daemon/digest.c (limited to 'daemon/digest.c') diff --git a/daemon/digest.c b/daemon/digest.c new file mode 100644 index 0000000..51120a0 --- /dev/null +++ b/daemon/digest.c @@ -0,0 +1,533 @@ + +#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(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(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)); + 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, unsigned char* nonce, + const char* realm, const char* domains, int stale) +{ + ASSERT(realm); + ASSERT(nonce); + + ha_bufmcat(buf, HA_PREFIX_DIGEST, " realm=\"", realm, "\", nonce=\"", NULL); + ha_bufjoin(buf); + ha_bufenc64(buf, nonce, DIGEST_NONCE_LEN); + ha_bufjoin(buf); + ha_bufmcat(buf, "\", 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) +{ + /* + * This function destroys the contents of header by + * terminating strings in it all over the place. + */ + + char next; + char* key; + char* value; + + header = ha_bufcpy(buf, header); + + if(!header) + return HA_ERROR; + + 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 == '=') + { + header++; + + while(header[0] && isspace(header[0])) + header++; + + if(header[0] == '\"') /* quoted string */ + { + header++; + value = header; + + while(header[0] && header[0] != '\"') + header++; + + header[0] = 0; + header++; + } + + else /* token */ + { + value = header; + + while(header[0] && header[0] != ',' && !isspace(header[0])) + header++; + + header[0] = 0; + header++; + } + + 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) + { + void* d = ha_bufdec64(buf, rec->nonce, DIGEST_NONCE_LEN); + + if(d != NULL) + memcpy(nonce, d, DIGEST_NONCE_LEN); + } + } + + return HA_OK; +} + +int digest_check(const char* realm, const char* method, const char* uri, + ha_buffer_t* buf, digest_header_t* dg, digest_record_t* rec) +{ + unsigned char hash[MD5_LEN]; + md5_ctx_t md5; + const char* digest; + const char* t; + + /* Check for digest */ + if(!dg->digest || !dg->digest[0]) + { + ha_messagex(LOG_WARNING, "digest response missing digest"); + return HA_FALSE; + } + + /* Username */ + if(!dg->username || !dg->username[0] || + md5_strcmp(rec->userhash, dg->username) != 0) + { + ha_messagex(LOG_WARNING, "digest response missing username"); + return HA_FALSE; + } + + /* The realm */ + if(!dg->realm || strcmp(dg->realm, realm) != 0) + { + ha_messagex(LOG_WARNING, "digest response contains invalid realm: '%s'", + dg->realm ? 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_FALSE; + } + + /* The cnonce */ + if(!dg->cnonce || !dg->cnonce[0]) + { + ha_messagex(LOG_WARNING, "digest response is missing cnonce value"); + return HA_FALSE; + } + + /* The nonce count */ + if(!dg->nc || !dg->nc[0]) + { + ha_messagex(LOG_WARNING, "digest response is missing nc value"); + return HA_FALSE; + } + + /* Validate the nc */ + else + { + char* e; + long nc = strtol(dg->nc, &e, 10); + + if(e != (dg->nc + strlen(e)) || nc != rec->nc) + { + ha_messagex(LOG_WARNING, "digest response has invalid nc value: %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_FALSE; + } + + /* Request URI */ + if(!dg->uri) + { + ha_messagex(LOG_WARNING, "digest response is missing uri"); + return HA_FALSE; + } + + if(strcmp(dg->uri, uri) != 0) + { + ha_uri_t d_uri; + ha_uri_t s_uri; + + if(ha_uriparse(buf, dg->uri, &d_uri) == HA_ERROR) + { + ha_messagex(LOG_WARNING, "digest response constains invalid uri: %s", dg->uri); + return HA_FALSE; + } + + if(ha_uriparse(buf, uri, &s_uri) == HA_ERROR) + { + ha_messagex(LOG_ERR, "server sent us an invalid uri"); + return HA_ERROR; + } + + if(ha_uricmp(&d_uri, &s_uri) != 0) + { + ha_messagex(LOG_WARNING, "digest response contains wrong uri: %s " + "(should be %s)", dg->uri, uri); + return HA_ERROR; + } + } + + /* + * 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_ERROR; + + /* 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_ERROR; + + + /* 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_ERROR; + + 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; + + 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; + 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); +} \ No newline at end of file -- cgit v1.2.3