bin/121074: Add RFC2617 digest authentication to fetch(3)

Yen-Ming Lee leeym at FreeBSD.org
Mon Feb 25 10:00:02 UTC 2008


>Number:         121074
>Category:       bin
>Synopsis:       Add RFC2617 digest authentication to fetch(3)
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Feb 25 10:00:01 UTC 2008
>Closed-Date:
>Last-Modified:
>Originator:     Yen-Ming Lee
>Release:        FreeBSD 6.2-RELEASE i386
>Organization:
>Environment:
System: FreeBSD db1.leeym.com 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40:27 UTC 2007 root at dessler.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC i386


	
>Description:

Add RFC2617 digest authentication.
qop=auth-int and non-MD5 algorithm are not implemented yet.
http://www.freebsd.org/projects/ideas/#p-libfetchauth

>How-To-Repeat:
	
>Fix:

	

--- libfetchauth.diff begins here ---
Index: Makefile
===================================================================
RCS file: /home/ncvs/src/lib/libfetch/Makefile,v
retrieving revision 1.51
diff -u -r1.51 Makefile
--- Makefile	19 Dec 2007 05:10:07 -0000	1.51
+++ Makefile	25 Feb 2008 09:49:50 -0000
@@ -21,6 +21,7 @@
 .endif
 
 CFLAGS+=	-DFTP_COMBINE_CWDS
+LDADD+=		-lmd
 
 CSTD?=		c99
 WARNS?=		2
Index: http.c
===================================================================
RCS file: /home/ncvs/src/lib/libfetch/http.c,v
retrieving revision 1.84
diff -u -r1.84 http.c
--- http.c	8 Feb 2008 09:48:48 -0000	1.84
+++ http.c	25 Feb 2008 09:49:50 -0000
@@ -75,6 +75,7 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <md5.h>
 
 #include <netinet/in.h>
 #include <netinet/tcp.h>
@@ -127,6 +128,127 @@
 };
 
 /*
+ * Authentication scheme types
+ */
+typedef enum {
+	auth_unknown = -1,
+	auth_basic = 0,
+	auth_digest = 1,
+} auth_scheme_t;
+
+/*
+ * Information needed to construct request_digest
+ */
+struct httpdigest
+{
+	/* RFC 2617: 3.2.1 digest-challenge */
+  	char		*realm;
+	char		*domain;
+	char		*nonce;
+	char		*opaque;
+	char		*stale;
+	char		*algorithm;
+	char		*qop;
+	/* RFC 2617: 3.2.2.3 A2 */
+	char		*method;
+	char		*uri;
+};
+
+/*
+ * Free a digest
+ */
+void
+http_free_digest(struct httpdigest *d)
+{
+	if (!d)
+		return;
+	if (d->realm)
+		free(d->realm);
+	if (d->domain)
+		free(d->domain);
+	if (d->nonce)
+		free(d->nonce);
+	if (d->opaque)
+		free(d->opaque);
+	if (d->stale)
+		free(d->stale);
+	if (d->algorithm)
+		free(d->algorithm);
+	if (d->qop)
+		free(d->qop);
+	if (d->method)
+		free(d->method);
+	if (d->uri)
+		free(d->uri);
+	free(d);
+}
+
+static char *
+http_parse_value(const char *hdr, const char *key)
+{
+	char *begin, *_key, *end, *value, *sep = ",";
+	int len;
+	len = strlen(key);
+	if ((_key = malloc(len+1)) == NULL)
+		return NULL;
+	sprintf(_key, "%s=", key);
+	begin = strcasestr(hdr, _key);
+	if (!begin)
+		goto ouch;
+	begin += len + 1;
+	if (*begin == '"') {
+		begin++;
+		sep = "\"";
+	}
+	end = strstr(begin, sep);
+	if (!end)
+		goto ouch;
+	len = end - begin + 1;
+	value = malloc(len);
+	strlcpy(value, begin, len);
+	return value;
+
+ouch:
+	free(_key);
+	return NULL;
+}
+
+/*
+ * Construct a digest based on WWW-Authenticate header
+ */
+auth_scheme_t
+http_parse_authenticate(const char *p, const char *method, const char *uri, struct httpdigest *d)
+{
+	/* Only handle "digest" authentication scheme */
+	if (strncasecmp(p, "Basic", 5) == 0) {
+		return auth_basic;
+	} else if (strncasecmp(p, "Digest", 6)) {
+		return auth_unknown;
+	}
+
+	d->realm = http_parse_value(p, "realm");
+	d->domain = http_parse_value(p, "domain");
+	d->nonce = http_parse_value(p, "nonce");
+	d->opaque = http_parse_value(p, "opaque");
+	d->stale = http_parse_value(p, "stale");
+	d->algorithm = http_parse_value(p, "algorithm");
+	d->qop = http_parse_value(p, "qop");
+	d->method = strdup(method);
+	d->uri = strdup(uri);
+
+	if (!d->realm || !d->nonce || !d->method || !d->uri) {
+		http_seterr(HTTP_PROTOCOL_ERROR);
+		goto ouch;
+	}
+
+	return auth_digest;
+
+ouch:
+	http_free_digest(d);
+	return auth_unknown;
+}
+
+/*
  * Get next chunk header
  */
 static int
@@ -617,6 +739,18 @@
 }
 
 /*
+ * MD5 encoding
+ */
+static char *
+http_md5(const char *src, char *dst)
+{
+	MD5_CTX Md5Ctx;
+	MD5Init(&Md5Ctx);
+	MD5Update(&Md5Ctx, src, strlen(src));
+	return MD5End(&Md5Ctx, dst);
+}
+
+/*
  * Encode username and password
  */
 static int
@@ -639,10 +773,95 @@
 }
 
 /*
+ * RFC 2617: Digest Access Authentication
+ */
+static int
+http_digest_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd, struct httpdigest *d)
+{
+	char buf[8192];
+	char HA1[33], HA2[33], request_digest[33];
+	char cnonce[9];
+	int r;
+	static int nc = 0;
+
+	DEBUG(fprintf(stderr, "usr: [%s]\n", usr));
+	DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd));
+
+	if (!d || !d->realm)
+		return(-1);
+	++nc;
+	sprintf(cnonce, "%08X", time(NULL));
+
+	/* RFC 2617: 3.2.2.2 A1 */
+	if (!d->algorithm || strcasecmp(d->algorithm, "MD5") == 0) {
+		sprintf(buf, "%s:%s:%s", usr, d->realm, pwd);
+	} else if (strcasecmp(d->algorithm, "MD5-sess") == 0) {
+		sprintf(buf, "%s:%s:%s", usr, d->realm, pwd);
+		http_md5(buf, HA1);
+		strcpy(buf, HA1);
+		strcat(buf, ":");
+		strcat(buf, d->nonce);
+		strcat(buf, ":");
+		strcat(buf, cnonce);
+	} else {
+		warnx("http_digest_auth(): non-MD5 algorithm not implemented");
+		return (-1);
+	}
+	http_md5(buf, HA1);
+
+	/* RFC 2617: 3.2.2.3 A2 */
+	if (!d->qop || strcasecmp(d->qop, "auth") == 0) {
+		sprintf(buf, "%s:%s", d->method, d->uri);
+		http_md5(buf, HA2);
+	} else if (strcasecmp(d->qop, "auth-int") == 0) {
+		warnx("http_digest_auth(): qop=auth-int not implemented");
+		return (-1);
+	}
+	/* RFC 2617: 3.2.2.1 Request-Digest */
+	if (!d->qop) {
+		sprintf(buf, "%s:%s:%s", HA1, d->nonce, HA2);
+		http_md5(buf, request_digest);
+	} else if (strcasecmp(d->qop, "auth") == 0 || strcasecmp(d->qop, "auth-int") == 0) {
+		sprintf(buf, "%s:%s:%08d:%s:%s:%s", HA1, d->nonce, nc, cnonce, d->qop, HA2);
+		http_md5(buf, request_digest);
+	}
+	if (d->qop) {
+		sprintf(buf,
+			"username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=\"%s\", nc=\"%08d\", cnonce=\"%s\", response=\"%s\"",
+			usr, d->realm, d->nonce, d->uri, d->qop, nc, cnonce, request_digest);
+	} else {
+		sprintf(buf,
+			"username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=\"%s\", response=\"%s\"",
+			usr, d->realm, d->nonce, d->uri, d->qop, request_digest);
+	}
+	if (d->opaque) {
+	  	strcat(buf, ", opaque=\"");
+	  	strcat(buf, d->opaque);
+	  	strcat(buf, "\"");
+	}
+	r = http_cmd(conn, "%s: Digest %s", hdr, buf);
+	return (r);
+}
+
+/*
+ * Send an authorization header based on authentication scheme
+ */
+static int
+http_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd, auth_scheme_t auth, struct httpdigest *d)
+{
+	if (auth == auth_basic)
+		return http_basic_auth(conn, hdr, usr, pwd);
+	else if (auth == auth_digest)
+	  	return http_digest_auth(conn, hdr, usr, pwd, d);
+	else
+		return (-1);
+}
+
+/*
  * Send an authorization header
  */
 static int
-http_authorize(conn_t *conn, const char *hdr, const char *p)
+http_authorize(conn_t *conn, const char *hdr, const char *p, struct httpdigest *digest)
 {
 	/* basic authorization */
 	if (strncasecmp(p, "basic:", 6) == 0) {
@@ -663,6 +882,26 @@
 		free(str);
 		return (r);
 	}
+	/* digest authorization */
+	else if (strncasecmp(p, "digest:", 7) == 0) {
+		char *user, *pwd, *str;
+		int r;
+		if (!digest)
+			return (-1);
+		/* skip realm */
+		for (p += 7; *p && *p != ':'; ++p)
+			/* nothing */ ;
+		if (!*p || strchr(++p, ':') == NULL)
+			return (-1);
+		if ((str = strdup(p)) == NULL)
+			return (-1); /* XXX */
+		user = str;
+		pwd = strchr(user, ':');
+		*pwd++ = '\0';
+		r = http_digest_auth(conn, hdr, user, pwd, digest);
+		free(str);
+		return (r);
+	}
 	return (-1);
 }
 
@@ -807,6 +1046,12 @@
 	FILE *f;
 	hdr_t h;
 	char hbuf[MAXHOSTNAMELEN + 7], *host;
+	auth_scheme_t auth;
+	struct httpdigest *digest = calloc(1, sizeof(struct httpdigest));
+	if (digest == NULL) {
+		DEBUG(fprintf(stderr, "failed to allocate httpdigest\n"));
+		goto ouch;
+	}
 
 	direct = CHECK_FLAG('d');
 	noredirect = CHECK_FLAG('A');
@@ -888,23 +1133,29 @@
 				http_basic_auth(conn, "Proxy-Authorization",
 				    purl->user, purl->pwd);
 			else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
-				http_authorize(conn, "Proxy-Authorization", p);
+				http_authorize(conn, "Proxy-Authorization", p, digest);
 		}
 
 		/* server authorization */
 		if (need_auth || *url->user || *url->pwd) {
-			if (*url->user || *url->pwd)
-				http_basic_auth(conn, "Authorization", url->user, url->pwd);
-			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
-				http_authorize(conn, "Authorization", p);
+			if (*url->user || *url->pwd) {
+				http_auth(conn, "Authorization", url->user, url->pwd, auth, digest);
+			}
+			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0') {
+				http_authorize(conn, "Authorization", p, digest);
+			}
 			else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
-				http_basic_auth(conn, "Authorization", url->user, url->pwd);
+				http_auth(conn, "Authorization", url->user, url->pwd, auth, digest);
 			} else {
 				http_seterr(HTTP_NEED_AUTH);
 				goto ouch;
 			}
 		}
 
+		if (purl || need_auth || *url->user || *url->pwd) {
+			http_free_digest(digest);
+		}
+
 		/* other headers */
 		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
 			if (strcasecmp(p, "auto") == 0)
@@ -1038,9 +1289,9 @@
 				chunked = (strcasecmp(p, "chunked") == 0);
 				break;
 			case hdr_www_authenticate:
+				auth = http_parse_authenticate(p, op, url->doc, digest);
 				if (conn->err != HTTP_NEED_AUTH)
 					break;
-				/* if we were smarter, we'd check the method and realm */
 				break;
 			case hdr_end:
 				/* fall through */
--- libfetchauth.diff ends here ---


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list