summaryrefslogtreecommitdiff
path: root/daemon/digest.c
diff options
context:
space:
mode:
authorStef Walter <stef@memberwebs.com>2004-04-24 22:38:50 +0000
committerStef Walter <stef@memberwebs.com>2004-04-24 22:38:50 +0000
commitcbbe71752d7f9c6204ab0f16600fe7f10490f203 (patch)
tree365e6e472d239d117b5f849c45f3c08fc6617c0a /daemon/digest.c
parentff76efc3e5e1b0e4ca3b10b7402406f619509bba (diff)
Completed implementation of ldap/ntlm/simple handlers
Diffstat (limited to 'daemon/digest.c')
-rw-r--r--daemon/digest.c533
1 files changed, 533 insertions, 0 deletions
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 <syslog.h>
+
+/* 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