lynx-dev
[Top][All Lists]
Advanced

[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;
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]