[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Lynx-dev] Patch for digest authentication support
From: |
Ken Hornstein |
Subject: |
[Lynx-dev] Patch for digest authentication support |
Date: |
Wed, 13 Jul 2011 11:16:34 -0400 |
Greetings,
I implemented support for digest authentication to Lynx. It's not perfect
and it doesn't handle all of the corner cases completely, but it might
be a good starting point (specifically, it doesn't handle the case where
the nonce is rejected by the server as stale, but that might require some
more work at higher levels). I've attached it below; comments welcome, but
I'm not sure how much time I'll have to work on it.
--Ken
diff -u -r lynx2-8-7.old/WWW/Library/Implementation/HTAABrow.c
lynx2-8-7/WWW/Library/Implementation/HTAABrow.c
--- lynx2-8-7.old/WWW/Library/Implementation/HTAABrow.c Sun Feb 1 20:41:02 2009
+++ lynx2-8-7/WWW/Library/Implementation/HTAABrow.c Tue Jul 12 10:14:15 2011
@@ -94,6 +94,7 @@
HTList *valid_schemes; /* Valid authentic.schemes */
HTAssocList **scheme_specifics; /* Scheme specific params
*/
BOOL retry; /* Failed last time -- reprompt (or
whatever) */
+ unsigned noncecount; /* Nonce count; used by Digest auth */
} HTAASetup;
/*
@@ -402,6 +403,7 @@
StrAllocCopy(setup->ctemplate, ctemplate);
setup->valid_schemes = valid_schemes;
setup->scheme_specifics = scheme_specifics;
+ setup->noncecount = 1;
HTList_addObject(server->setups, (void *) setup);
@@ -716,6 +718,340 @@
return compose_auth_stringResult;
}
+/* static convert_hash_to_hex()
+ *
+ * Convert a hash function output to hex digits
+ *
+ * ON ENTRY:
+ * hash is the binary output of our hash function
+ *
+ * ON EXIT:
+ * hexdigits is the hexadecimal version of the hash output
+ */
+
+static void convert_hash_to_hex(const unsigned char *hash,
+ char *hexdigits)
+{
+ int i;
+ unsigned j;
+
+ for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
+ j = (hash[i] >> 4) & 0xf;
+ if (j <= 9)
+ hexdigits[i*2] = j + '0';
+ else
+ hexdigits[i*2] = j + 'a' - 10;
+ j = hash[i] & 0xf;
+ if (j <= 9)
+ hexdigits[i*2 + 1] = j + '0';
+ else
+ hexdigits[i*2 + 1] = j + 'a' - 10;
+ }
+
+ hexdigits[MD5_DIGEST_LENGTH * 2] = '\0';
+}
+
+/* static compose_digest_auth_string()
+ *
+ * COMPOSE Digest AUTHENTICATION STRING;
+ * PROMPTS FOR USERNAME AND PASSWORD IF NEEDED
+ *
+ * ON ENTRY:
+ * setup is the current server setup.
+ * method HTTP method we are using for this document
+ * docname document we are trying to access
+ *
+ * ON EXIT:
+ * returns a newly composed authorization string,
+ * NULL, if something fails.
+ * NOTE:
+ * Like throughout the entire AA package, no string or structure
+ * returned by AA package needs to (or should) be freed.
+ * Uses same package variables as compose_auth_string().
+ *
+ */
+static char *compose_digest_auth_string(HTAASetup * setup,
+ const char *method,
+ const char *docname)
+{
+#ifdef USE_SSL
+ char *username = NULL;
+ char *password = NULL;
+ char *realmname = NULL;
+ char *nonce = NULL;
+ char *msg = NULL;
+ char *qop_list = NULL;
+ char *qop = NULL;
+ char *opaque = NULL;
+ HTAARealm *realm;
+ char *theHost = NULL;
+ char *thePort = NULL;
+ char ncvalue[16];
+ unsigned len;
+ MD5_CTX md5ctx;
+ unsigned char HA1[MD5_DIGEST_LENGTH];
+ char HA1HEX[MD5_DIGEST_LENGTH * 2 + 1];
+ unsigned char HA2[MD5_DIGEST_LENGTH];
+ char HA2HEX[MD5_DIGEST_LENGTH * 2 + 1];
+ unsigned char cnonce[MD5_DIGEST_LENGTH];
+ char cnoncehex[MD5_DIGEST_LENGTH * 2 + 1];
+ unsigned char finaldigest[MD5_DIGEST_LENGTH];
+ char finaldigesthex[MD5_DIGEST_LENGTH * 2 + 1];
+#endif /* USE_SSL */
+
+ FREE(compose_auth_stringResult); /* From previous call */
+
+#ifndef USE_SSL
+ CTRACE((tfp, "Not compiled with SSL support, cannot use "
+ "Digest authentication"));
+ return NULL;
+#else /* ! USE_SSL */
+
+ realmname = HTAssocList_lookup(setup->scheme_specifics[HTAA_DIGEST],
+ "realm");
+ if (!realmname) {
+ CTRACE((tfp, "No realm name found in request"));
+ return NULL;
+ }
+
+ nonce = HTAssocList_lookup(setup->scheme_specifics[HTAA_DIGEST], "nonce");
+
+ if (! nonce) {
+ CTRACE((tfp, "No nonce supplied in request"));
+ return NULL;
+ }
+
+ qop_list = HTAssocList_lookup(setup->scheme_specifics[HTAA_DIGEST], "qop");
+
+ /*
+ * If given a QOP list, then parse it. Right now we only support auth
+ */
+
+ if (qop_list) {
+ char *s = qop_list, *p;
+
+ do {
+ p = strchr(s, ',');
+
+ if (strncmp("auth", s, p ? p - s : strlen(s)) == 0) {
+ qop = "auth";
+ break;
+ }
+
+ if (p)
+ s = p + 1;
+ } while (p != NULL);
+
+ if (! qop) {
+ CTRACE((tfp, "Unable to find \"auth\" in qop list: \"%s\"",
+ qop_list));
+ return NULL;
+ }
+ }
+
+ opaque = HTAssocList_lookup(setup->scheme_specifics[HTAA_DIGEST],
"opaque");
+
+ realm = HTAARealm_lookup(setup->server->realms, realmname);
+ if (!(realm &&
+ realm->username && *realm->username &&
+ realm->password) || setup->retry) {
+ if (!realm) {
+ CTRACE((tfp, "%s `%s' %s\n",
+ "compose_auth_string: realm:", realmname,
+ "not found -- creating"));
+ realm = HTAARealm_new(setup->server->realms,
+ realmname, NULL, NULL);
+ }
+#if 0
+ /* Not quite sure what to do about proxy auth now */
+ /*
+ * The template should be either the '*' global for everything on the
+ * server (always true for proxy authorization setups), or a path for
+ * the start of a protected limb, with no host field, but we'll check
+ * for a host anyway in case a WWW-Protection-Template header set an
+ * absolute URL instead of a path. If we do get a host from this, it
+ * will include the port. - FM
+ */
+ if ((!IsProxy) && using_proxy && setup->ctemplate) {
+ proxiedHost = HTParse(setup->ctemplate, "", PARSE_HOST);
+ if (proxiedHost && *proxiedHost != '\0') {
+ theHost = proxiedHost;
+ }
+ }
+#endif
+ /*
+ * If we didn't get a host field from the template, set up the host
+ * name and port from the setup->server elements. - FM
+ */
+ if (!theHost)
+ theHost = setup->server->hostname;
+ if (setup->server->portnumber > 0 &&
+ setup->server->portnumber != 80) {
+ HTSprintf0(&thePort, ":%d", setup->server->portnumber);
+ }
+ /*
+ * Set up the message for the username prompt, and then issue the
+ * prompt. The default username is included in the call to the
+ * prompting function, but the password is NULL-ed and always replaced.
+ * - FM
+ */
+ len = (strlen(realm->realmname) +
+ strlen(theHost ?
+ theHost : "??") + 50);
+ HTSprintf0(&msg, gettext("Username for '%s' at %s '%s%s':"),
+ realm->realmname,
+ /* (IsProxy ? "proxy" : "server"), */ "server",
+ (theHost ? theHost : "??"),
+ NonNull(thePort));
+ FREE(thePort);
+ username = realm->username;
+ password = NULL;
+ HTPromptUsernameAndPassword(msg, &username, &password, 0);
+
+ FREE(msg);
+ FREE(realm->username);
+ FREE(realm->password);
+ realm->username = username;
+ realm->password = password;
+
+ if (!realm->username || !realm->password) {
+ /*
+ * Signals to retry. - FM
+ */
+ return NULL;
+ } else if (*realm->username == '\0') {
+ /*
+ * Signals to abort. - FM
+ */
+ StrAllocCopy(compose_auth_stringResult, "");
+ return compose_auth_stringResult;
+ }
+ }
+
+ /*
+ * Calculate a reasonable length
+ */
+
+ len = 13 /* username="" */ + strlen(realm->username) + 10 /* realm="" */ +
+ strlen(realmname) + 10 /* nonce="" */ + strlen(nonce) + 9 /* uri=""
*/ +
+ strlen(docname) + 1 /* extra */ + 45 /* reponse="xx" */ +
+ 43 /* cnonce="xx" */ + 11 /* opaque ="xx" */ +
+ (opaque ? strlen(opaque) : 0) + 10 /* qop=auth */ + 13 /* nc=xx */ +
+ 16 /* just in case */;
+
+ compose_auth_stringResult = typecallocn(char, len);
+ if (! compose_auth_stringResult)
+ outofmem(__FILE__, "compose_auth_string");
+
+ /*
+ * For the exact details here, see RFC 2617
+ *
+ * First, calculate the hash of A1
+ */
+
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, realm->username, strlen(realm->username));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, realmname, strlen(realmname));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, realm->password, strlen(realm->password));
+ MD5_Final(HA1, &md5ctx);
+
+ convert_hash_to_hex(HA1, HA1HEX);
+
+ /* Calculate A2 */
+
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, method, strlen(method));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, "/", 1);
+ MD5_Update(&md5ctx, docname, strlen(docname));
+ MD5_Final(HA2, &md5ctx);
+
+ convert_hash_to_hex(HA2, HA2HEX);
+
+ /* Calculate our cnonce, if a qop was given; we just make it the same
+ * as our MD5 hash length so it is easier */
+
+ if (qop) {
+ if (RAND_bytes(cnonce, MD5_DIGEST_LENGTH) == 0) {
+ if (RAND_pseudo_bytes(cnonce, MD5_DIGEST_LENGTH) < 0) {
+ CTRACE((tfp, "Unable to get random bytes for cnonce"));
+ return NULL;
+ }
+ }
+ convert_hash_to_hex(cnonce, cnoncehex);
+ }
+
+ /*
+ * Get the nonce count value and increment it
+ */
+
+ sprintf(ncvalue, "%08x", setup->noncecount);
+ setup->noncecount++;
+
+ /*
+ * Calculate our final digest value
+ */
+
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, HA1HEX, strlen(HA1HEX));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, nonce, strlen(nonce));
+ MD5_Update(&md5ctx, ":", 1);
+ if (qop) {
+ MD5_Update(&md5ctx, ncvalue, strlen(ncvalue));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, cnoncehex, strlen(cnoncehex));
+ MD5_Update(&md5ctx, ":", 1);
+ MD5_Update(&md5ctx, qop, strlen(qop));
+ MD5_Update(&md5ctx, ":", 1);
+ }
+ MD5_Update(&md5ctx, HA2HEX, strlen(HA2HEX));
+ MD5_Final(finaldigest, &md5ctx);
+
+ convert_hash_to_hex(finaldigest, finaldigesthex);
+
+ /*
+ * Build the response string
+ */
+
+ strcpy(compose_auth_stringResult, "username=\"");
+ strcat(compose_auth_stringResult, realm->username);
+ strcat(compose_auth_stringResult, "\", ");
+ strcat(compose_auth_stringResult, "realm=\"");
+ strcat(compose_auth_stringResult, realmname);
+ strcat(compose_auth_stringResult, "\", ");
+ strcat(compose_auth_stringResult, "nonce=\"");
+ strcat(compose_auth_stringResult, nonce);
+ strcat(compose_auth_stringResult, "\", ");
+ strcat(compose_auth_stringResult, "uri=\"/");
+ strcat(compose_auth_stringResult, docname);
+ strcat(compose_auth_stringResult, "\", ");
+ if (qop) {
+ strcat(compose_auth_stringResult, "qop=\"");
+ strcat(compose_auth_stringResult, qop);
+ strcat(compose_auth_stringResult, "\", ");
+ strcat(compose_auth_stringResult, "cnonce=\"");
+ strcat(compose_auth_stringResult, cnoncehex);
+ strcat(compose_auth_stringResult, "\", ");
+ strcat(compose_auth_stringResult, "nc=\"");
+ strcat(compose_auth_stringResult, ncvalue);
+ strcat(compose_auth_stringResult, "\", ");
+ }
+ strcat(compose_auth_stringResult, "response=\"");
+ strcat(compose_auth_stringResult, finaldigesthex);
+ strcat(compose_auth_stringResult, "\"");
+ if (opaque) {
+ strcat(compose_auth_stringResult, ", opaque=\"");
+ strcat(compose_auth_stringResult, opaque);
+ strcat(compose_auth_stringResult, "\"");
+ }
+
+ return compose_auth_stringResult;
+#endif /* USE_SSL */
+}
+
/* BROWSER static HTAA_selectScheme()
* SELECT THE AUTHENTICATION SCHEME TO USE
* ON ENTRY:
@@ -795,6 +1131,7 @@
* ON ENTRY:
* hostname is the hostname of the server.
* portnumber is the portnumber in which the server runs.
+ * method is the method we're using to access this document
* docname is the pathname of the document (as in URL)
* IsProxy should be TRUE if this is a proxy.
*
@@ -808,6 +1145,7 @@
*/
char *HTAA_composeAuth(const char *hostname,
const int portnumber,
+ const char *method,
const char *docname,
BOOL IsProxy)
{
@@ -963,6 +1301,10 @@
case HTAA_BASIC:
case HTAA_PUBKEY:
auth_string = compose_auth_string(scheme, current_setup, IsProxy);
+ break;
+ case HTAA_DIGEST:
+ auth_string = compose_digest_auth_string(current_setup,
+ method, docname);
break;
case HTAA_KERBEROS_V4:
/* OTHER AUTHENTICATION ROUTINES ARE CALLED HERE */
diff -u -r lynx2-8-7.old/WWW/Library/Implementation/HTAABrow.h
lynx2-8-7/WWW/Library/Implementation/HTAABrow.h
--- lynx2-8-7.old/WWW/Library/Implementation/HTAABrow.h Sun Jan 2 18:35:21 2005
+++ lynx2-8-7/WWW/Library/Implementation/HTAABrow.h Mon Jul 11 20:27:02 2011
@@ -60,6 +60,7 @@
* ON ENTRY:
* hostname is the hostname of the server.
* portnumber is the portnumber in which the server runs.
+ * method is the method we're using to access this document
* docname is the pathname of the document (as in URL)
*
* ON EXIT:
@@ -71,6 +72,7 @@
* As usual, this string is automatically freed.
*/ extern char *HTAA_composeAuth(const char *hostname,
const int portnumber,
+ const char *method,
const char *docname,
BOOL IsProxy);
Only in lynx2-8-7/WWW/Library/Implementation: HTAABrow.o
Only in lynx2-8-7/WWW/Library/Implementation: HTAAProt.o
diff -u -r lynx2-8-7.old/WWW/Library/Implementation/HTAAUtil.c
lynx2-8-7/WWW/Library/Implementation/HTAAUtil.c
--- lynx2-8-7.old/WWW/Library/Implementation/HTAAUtil.c Wed Dec 31 20:22:26 2008
+++ lynx2-8-7/WWW/Library/Implementation/HTAAUtil.c Fri Jul 8 15:57:09 2011
@@ -92,6 +92,9 @@
} else if (!strncmp(upcased, "KERBEROSV5", 10)) {
FREE(upcased);
return HTAA_KERBEROS_V5;
+ } else if (!strncmp(upcased, "DIGEST", 6)) {
+ FREE(upcased);
+ return HTAA_DIGEST;
} else {
FREE(upcased);
return HTAA_UNKNOWN;
@@ -121,6 +124,8 @@
return "KerberosV4";
case HTAA_KERBEROS_V5:
return "KerberosV5";
+ case HTAA_DIGEST:
+ return "Digest";
case HTAA_UNKNOWN:
return "UNKNOWN";
default:
diff -u -r lynx2-8-7.old/WWW/Library/Implementation/HTAAUtil.h
lynx2-8-7/WWW/Library/Implementation/HTAAUtil.h
--- lynx2-8-7.old/WWW/Library/Implementation/HTAAUtil.h Sun Jan 2 18:35:21 2005
+++ lynx2-8-7/WWW/Library/Implementation/HTAAUtil.h Fri Jul 8 15:55:30 2011
@@ -61,6 +61,7 @@
HTAA_PUBKEY,
HTAA_KERBEROS_V4,
HTAA_KERBEROS_V5,
+ HTAA_DIGEST,
HTAA_MAX_SCHEMES /* THIS MUST ALWAYS BE LAST! Number of schemes
*/
} HTAAScheme;
- [Lynx-dev] Patch for digest authentication support,
Ken Hornstein <=