You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ro...@locus.apache.org on 2000/03/02 06:01:02 UTC

cvs commit: apache-1.3/src/modules/experimental mod_auth_digest.c

ronald      00/03/01 21:01:02

  Modified:    src/modules/experimental mod_auth_digest.c
  Log:
  - Reworked MD5-sess stuff. The semantics of userpw_hash() have been
    changed
    for it to return
        MD5(MD5(username ":" realm ":" password) ":" nonce ":" cnonce)
    instead of just
        MD5(username ":" realm ":" password)
    because one of the points of MD5-sess is to allow the info to be retrieved
    from login servers so that the server itself never has the full auth info
    (after all, MD5(u/r/p) is equivalent to the password for auth purposes).
    Some minor changes such as renaming get_session() to get_session_HA1()
    and adding internal checks were done too.
  
  - Also, in order to allow for servers to share a realm the server-name
      and port have been removed from the nonce-hash. Even so, sharing the
    realm has problems - see the new comments at the beginning.
  
  - Renamed all variables called "new" to something that won't cause
    problems
    under a C++ compiler.
  
  - Some minor fixes.
  
  Revision  Changes    Path
  1.14      +119 -67   apache-1.3/src/modules/experimental/mod_auth_digest.c
  
  Index: mod_auth_digest.c
  ===================================================================
  RCS file: /home/cvs/apache-1.3/src/modules/experimental/mod_auth_digest.c,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- mod_auth_digest.c	2000/01/12 15:23:23	1.13
  +++ mod_auth_digest.c	2000/03/02 05:01:02	1.14
  @@ -78,8 +78,20 @@
    *     currently ignored by mod_proxy (needs patch to mod_proxy)
    *   - generating the secret takes a while (~ 8 seconds) if using the
    *     truerand library
  + *   - The source of the secret should be run-time directive (with server
  + *     scope: RSRC_CONF). However, that could be tricky when trying to
  + *     choose truerand vs. file...
    *   - shared-mem not completely tested yet. Seems to work ok for me,
    *     but... (definitely won't work on Windoze)
  + *   - Sharing a realm among multiple servers has following problems:
  + *     o Server name and port can't be included in nonce-hash
  + *       (we need two nonce formats, which must be configured explicitly)
  + *     o Nonce-count check can't be for equal, or then nonce-count checking
  + *       must be disabled. What we could do is the following:
  + *       (expected < received) ? set expected = received : issue error
  + *       The only problem is that it allows replay attacks when somebody
  + *       captures a packet sent to one server and sends it to another
  + *       one. Should we add "AuthDigestNcCheck Strict"?
    */
   
   /* The section for the Configure script:
  @@ -306,7 +318,8 @@
   {
   #ifdef	DEV_RANDOM
       int rnd;
  -    size_t got, tot;
  +    ssize_t got;
  +    size_t tot;
   #else
       extern int randbyte(void);	/* from the truerand library */
       unsigned int idx;
  @@ -764,9 +777,9 @@
   
   /*
    * Add a new client to the list. Returns the entry if successful, NULL
  - * otherwise. This triggers the garbage collection is memory is low.
  + * otherwise. This triggers the garbage collection if memory is low.
    */
  -static client_entry *add_client(unsigned long key, client_entry *new,
  +static client_entry *add_client(unsigned long key, client_entry *info,
   				server_rec *s)
   {
       int bucket;
  @@ -797,7 +810,7 @@
   
       /* now add the entry */
   
  -    memcpy(entry, new, sizeof(client_entry));
  +    memcpy(entry, info, sizeof(client_entry));
       entry->key  = key;
       entry->next = client_list->table[bucket];
       client_list->table[bucket] = entry;
  @@ -964,8 +977,8 @@
    * Nonce generation code
    */
   
  -/* The hash part of the nonce is a SHA-1 hash of the time, realm, opaque,
  - * and our secret.
  +/* The hash part of the nonce is a SHA-1 hash of the time, realm, server host
  + * and port, opaque, and our secret.
    */
   static void gen_nonce_hash(char *hash, const char *timestr, const char *opaque,
   			   const server_rec *server,
  @@ -977,10 +990,12 @@
       int idx;
   
       memcpy(&ctx, &conf->nonce_ctx, sizeof(ctx));
  +    /*
       ap_SHA1Update_binary(&ctx, (const unsigned char *) server->server_hostname,
   			 strlen(server->server_hostname));
       ap_SHA1Update_binary(&ctx, (const unsigned char *) &server->port,
   			 sizeof(server->port));
  +     */
       ap_SHA1Update_binary(&ctx, (const unsigned char *) timestr, strlen(timestr));
       if (opaque)
   	ap_SHA1Update_binary(&ctx, (const unsigned char *) opaque,
  @@ -1035,7 +1050,7 @@
   static client_entry *gen_client(const request_rec *r)
   {
       unsigned long op;
  -    client_entry new = { 0, NULL, 0, "", "" }, *entry;
  +    client_entry new_entry = { 0, NULL, 0, "", "" }, *entry;
   
       if (!opaque_mm)  return 0;
   
  @@ -1043,7 +1058,7 @@
       op = (*opaque_cntr)++;
       mm_unlock(opaque_mm);
   
  -    if (!(entry = add_client(op, &new, r->server))) {
  +    if (!(entry = add_client(op, &new_entry, r->server))) {
   	ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
   		      "Digest: failed to allocate client entry - ignoring "
   		      "client");
  @@ -1062,53 +1077,70 @@
    * MD5-sess code.
    *
    * If you want to use algorithm=MD5-sess you must write get_userpw_hash()
  - * yourself (see below). The dummy provided here just returns the hash
  - * from the auth-file, i.e. it is only useful for testing client
  - * implementations of MD5-sess .
  + * yourself (see below). The dummy provided here just uses the hash from
  + * the auth-file, i.e. it is only useful for testing client implementations
  + * of MD5-sess .
    */
   
   /*
    * get_userpw_hash() will be called each time a new session needs to be
    * generated and is expected to return the equivalent of
    *
  + * h_urp = ap_md5(r->pool,
  + *         ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
    * ap_md5(r->pool,
  - *        ap_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd))
  + *         (unsigned char *) ap_pstrcat(r->pool, h_urp, ":", resp->nonce, ":",
  + *                                      resp->cnonce, NULL));
  + *
  + * or put differently, it must return
  + *
  + *   MD5(MD5(username ":" realm ":" password) ":" nonce ":" cnonce)
    *
  - * You must implement this yourself, and will probably consist of code
  - * contacting the password server and retrieving the hash from it.
  + * If something goes wrong, the failure must be logged and NULL returned.
    *
  + * You must implement this yourself, which will probably consist of code
  + * contacting the password server with the necessary information (typically
  + * the username, realm, nonce, and cnonce) and receiving the hash from it.
  + *
    * TBD: This function should probably be in a seperate source file so that
  - * people need not modify mod_auth_digest.c each time they install a new version
  - * of apache.
  + * people need not modify mod_auth_digest.c each time they install a new
  + * version of apache.
    */
   static const char *get_userpw_hash(const request_rec *r,
   				   const digest_header_rec *resp,
   				   const digest_config_rec *conf)
   {
  -    /* for now, just get it from pwfile */
  -    return conf->ha1;
  +    return ap_md5(r->pool,
  +	     (unsigned char *) ap_pstrcat(r->pool, conf->ha1, ":", resp->nonce,
  +					  ":", resp->cnonce, NULL));
   }
   
   
  -static const char *get_session(const request_rec *r,
  -			       digest_header_rec *resp,
  -			       const digest_config_rec *conf)
  -{
  -    const char *ha1 = NULL, *urp;
  -
  -    /* get ha1 from client list */
  -    if (resp->opaque && resp->client)
  -	ha1 = resp->client->ha1;
  -
  -    /* generate new session if necessary */
  -    if (ha1 == NULL || ha1[0] == '\0') {
  -	urp = get_userpw_hash(r, resp, conf);
  -	ha1 = ap_md5(r->pool,
  -		     (unsigned char *) ap_pstrcat(r->pool, urp, ":", resp->nonce,
  -						  ":", resp->cnonce, NULL));
  -	if (!resp->client)
  -	    resp->client = gen_client(r);
  -	if (resp->client)
  +/* Retrieve current session H(A1). If there is none and "generate" is
  + * true then a new session for MD5-sess is generated and stored in the
  + * client struct; if generate is false, or a new session could not be
  + * generated then NULL is returned (in case of failure to generate the
  + * failure reason will have been logged already).
  + */
  +static const char *get_session_HA1(const request_rec *r,
  +				   digest_header_rec *resp,
  +				   const digest_config_rec *conf,
  +				   int generate)
  +{
  +    const char *ha1 = NULL;
  +
  +    /* return the current sessions if there is one */
  +    if (resp->opaque && resp->client && resp->client->ha1[0])
  +	return resp->client->ha1;
  +    else if (!generate)
  +	return NULL;
  +
  +    /* generate a new session */
  +    if (!resp->client)
  +	resp->client = gen_client(r);
  +    if (resp->client) {
  +	ha1 = get_userpw_hash(r, resp, conf);
  +	if (ha1)
   	    memcpy(resp->client->ha1, ha1, sizeof(resp->client->ha1));
       }
   
  @@ -1221,11 +1253,6 @@
   	qop = ap_pstrcat(r->pool, qop, "\"", NULL);
       }
   
  -    /* MD5-sess stuff */
  -
  -    if (!stale && !strcasecmp(conf->algorithm, "MD5-sess"))
  -	clear_session(resp);
  -
       /* Setup opaque */
   
       if (resp->opaque == NULL) {
  @@ -1265,6 +1292,14 @@
       if (resp->client && conf->nonce_lifetime == 0)
   	memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1);
   
  +    /* Setup MD5-sess stuff. Note that we just clear out the session
  +     * info here, since we can't generate a new session until the request
  +     * from the client comes in with the cnonce.
  +     */
  +
  +    if (!strcasecmp(conf->algorithm, "MD5-sess"))
  +	clear_session(resp);
  +
       /* setup domain attribute. We want to send this attribute wherever
        * possible so that the client won't send the Authorization header
        * unneccessarily (it's usually > 200 bytes!).
  @@ -1332,28 +1367,29 @@
   static int check_nc(const request_rec *r, const digest_header_rec *resp,
   		    const digest_config_rec *conf)
   {
  -    if (conf->check_nc && client_mm) {
  -	unsigned long nc;
  +    unsigned long nc;
  +    const char *snc = resp->nonce_count;
  +    char *endptr;
   
  -	const char *snc = resp->nonce_count;
  -	char *endptr;
  +    if (!conf->check_nc || !client_mm)
  +	return OK;
   
  -	nc = strtol(snc, &endptr, 16);
  -	if (endptr < (snc+strlen(snc)) && !ap_isspace(*endptr)) {
  -	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  -			  "Digest: invalid nc %s received - not a number", snc);
  -	    return !OK;
  -	}
  +    nc = strtol(snc, &endptr, 16);
  +    if (endptr < (snc+strlen(snc)) && !ap_isspace(*endptr)) {
  +	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  +		      "Digest: invalid nc %s received - not a number", snc);
  +	return !OK;
  +    }
   
  -	if (!resp->client)
  -	    return !OK;
  +    if (!resp->client)
  +	return !OK;
   
  -	if (nc != resp->client->nonce_count) {
  -	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
  -			  "nonce-count check failed: %lu != %lu", nc,
  -			  resp->client->nonce_count);
  -	    return !OK;
  -	}
  +    if (nc != resp->client->nonce_count) {
  +	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  +		      "Digest: Warning, possible replay attack: nonce-count "
  +		      "check failed: %lu != %lu", nc,
  +		      resp->client->nonce_count);
  +	return !OK;
       }
   
       return OK;
  @@ -1423,12 +1459,12 @@
   
   /* The actual MD5 code... whee */
   
  +/* RFC-2069 */
   static const char *old_digest(const request_rec *r,
   			      const digest_header_rec *resp, const char *ha1)
   {
       const char *ha2;
   
  -    /* rfc-2069 */
       ha2 = ap_md5(r->pool, (unsigned char *)ap_pstrcat(r->pool, r->method, ":",
   						      resp->uri, NULL));
       return ap_md5(r->pool,
  @@ -1436,15 +1472,18 @@
   					      ":", ha2, NULL));
   }
   
  +/* RFC-2617 */
   static const char *new_digest(const request_rec *r,
   			      digest_header_rec *resp,
   			      const digest_config_rec *conf)
   {
       const char *ha1, *ha2, *a2;
   
  -    /* draft-ietf-http-authentication-03 */
  -    if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess"))
  -	ha1 = get_session(r, resp, conf);
  +    if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) {
  +	ha1 = get_session_HA1(r, resp, conf, 1);
  +	if (!ha1)
  +	    return NULL;
  +    }
       else
   	ha1 = conf->ha1;
   
  @@ -1675,6 +1714,7 @@
   	}
       }
       else {
  +	const char *exp_digest;
   	int match = 0, idx;
   	for (idx=0; conf->qop_list[idx] != NULL; idx++) {
   	    if (!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
  @@ -1693,7 +1733,12 @@
   	    return AUTH_REQUIRED;
   	}
   
  -	if (strcmp(resp->digest, new_digest(r, resp, conf))) {
  +	exp_digest = new_digest(r, resp, conf);
  +	if (!exp_digest) {
  +	    /* we failed to allocate a client struct */
  +	    return SERVER_ERROR;
  +	}
  +	if (strcmp(resp->digest, exp_digest)) {
   	    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
   			  "Digest: user %s: password mismatch: %s", conn->user,
   			  r->uri);
  @@ -1945,8 +1990,15 @@
   
   	/* calculate rspauth attribute
   	 */
  -	if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess"))
  -	    ha1 = get_session(r, resp, conf);
  +	if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) {
  +	    ha1 = get_session_HA1(r, resp, conf, 0);
  +	    if (!ha1) {
  +		ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
  +			      "Digest: internal error: couldn't find session "
  +			      "info for user %s", resp->username);
  +		return !OK;
  +	    }
  +	}
   	else
   	    ha1 = conf->ha1;