coar 99/02/03 08:22:34
Modified: . STATUS src ApacheCore.def CHANGES src/ap ap_md5c.c src/include ap_md5.h ap_mmn.h src/modules/standard mod_auth.c mod_auth_db.c mod_auth_dbm.c src/support htpasswd.c httpd.exp Log: Rework the ap_MD5Encode() routine to use FreeBSD's algorithm and a private significator ("$apr1"); also make it reentrant. Abstract the password checking into a new routine, ap_validate_password(plaintext, hashed), and modify mod_auth*.c to use it instead of each doing the algorithm check. Obtained from: FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c (MD5) Revision Changes Path 1.610 +1 -2 apache-1.3/STATUS Index: STATUS =================================================================== RCS file: /home/cvs/apache-1.3/STATUS,v retrieving revision 1.609 retrieving revision 1.610 diff -u -r1.609 -r1.610 --- STATUS 1999/02/02 16:15:50 1.609 +++ STATUS 1999/02/03 16:22:25 1.610 @@ -1,5 +1,5 @@ 1.3 STATUS: - Last modified at [$Date: 1999/02/02 16:15:50 $] + Last modified at [$Date: 1999/02/03 16:22:25 $] Release: @@ -15,7 +15,6 @@ RELEASE SHOWSTOPPERS: - * md5 passwd stuff incompatible with FreeBSD implementation. RELEASE NON-SHOWSTOPPERS BUT WOULD BE REAL NICE TO WRAP THESE UP: 1.9 +1 -0 apache-1.3/src/ApacheCore.def Index: ApacheCore.def =================================================================== RCS file: /home/cvs/apache-1.3/src/ApacheCore.def,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- ApacheCore.def 1999/01/29 14:28:53 1.8 +++ ApacheCore.def 1999/02/03 16:22:27 1.9 @@ -326,4 +326,5 @@ ap_os_is_filename_valid @319 ap_find_opaque_token @320 ap_MD5Encode @321 + ap_validate_password @322 1.1229 +5 -0 apache-1.3/src/CHANGES Index: CHANGES =================================================================== RCS file: /home/cvs/apache-1.3/src/CHANGES,v retrieving revision 1.1228 retrieving revision 1.1229 diff -u -r1.1228 -r1.1229 --- CHANGES 1999/01/30 19:19:23 1.1228 +++ CHANGES 1999/02/03 16:22:28 1.1229 @@ -1,5 +1,10 @@ Changes with Apache 1.3.5 + *) Rework the MD5 authentication scheme to use FreeBSD's algorithm, + and use a private significator ('$apr1$') to mark passwords as + being smashed with our own algorithm. Also abstract the password + checking into a new ap_validate_password() routine. [Ken Coar] + *) Win32: The filename validity checker now allows "COM" but refuses access to "COM1" through "COM4". This allows filenames such as "com.name" to be served. [Paul Sutton] PR#3769. 1.19 +217 -43 apache-1.3/src/ap/ap_md5c.c Index: ap_md5c.c =================================================================== RCS file: /home/cvs/apache-1.3/src/ap/ap_md5c.c,v retrieving revision 1.18 retrieving revision 1.19 diff -u -r1.18 -r1.19 --- ap_md5c.c 1999/01/25 22:55:36 1.18 +++ ap_md5c.c 1999/02/03 16:22:30 1.19 @@ -88,6 +88,17 @@ * */ +/* + * The ap_MD5Encode() routine uses much code obtained from the FreeBSD 3.0 + * MD5 crypt() function, which is licenced as follows: + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <[EMAIL PROTECTED]> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + */ + #include <string.h> #include "ap_config.h" @@ -381,7 +392,7 @@ } /* Decodes input (unsigned char) into output (UINT4). Assumes len is - a multiple of 4. + * a multiple of 4. */ static void Decode(UINT4 *output, const unsigned char *input, unsigned int len) { @@ -391,47 +402,210 @@ output[i] = ((UINT4) input[j]) | (((UINT4) input[j + 1]) << 8) | (((UINT4) input[j + 2]) << 16) | (((UINT4) input[j + 3]) << 24); } + +/* + * Define the Magic String prefix that identifies a password as being + * hashed using our algorithm. + */ +static const char *apr1_id = "$apr1$"; + +/* + * The following MD5 password encryption code was largely borrowed from + * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is + * licenced as stated at the top of this file. + */ + +static void to64 __P((char *, unsigned long, int)); + +static void to64(char *s, unsigned long v, int n) +{ +static void to64 __P((char *, unsigned long, int)); + static unsigned char itoa64[] = /* 0 ... 63 => ASCII - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + while (--n >= 0) { + *s++ = itoa64[v&0x3f]; + v >>= 6; + } +} + +API_EXPORT(void) ap_MD5Encode(const char *pw, const char *salt, + char *result, size_t nbytes) +{ + /* + * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL, + * plus 4 for the '$' separators, plus the password hash itself. + * Let's leave a goodly amount of leeway. + */ + + char passwd[120], *p; + const char *sp, *ep; + unsigned char final[16]; + int sl, pl, i; + AP_MD5_CTX ctx, ctx1; + unsigned long l; + + /* + * Refine the salt first. It's possible we were given an already-hashed + * string as the salt argument, so extract the actual salt value from it + * if so. Otherwise just use the string up to the first '$' as the salt. + */ + sp = salt; + + /* + * If it starts with the magic string, then skip that. + */ + if (!strncmp(sp, apr1_id, strlen(apr1_id))) { + sp += strlen(apr1_id); + } + + /* + * It stops at the first '$' or 8 chars, whichever comes first + */ + for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) { + continue; + } + + /* + * Get the length of the true salt + */ + sl = ep - sp; + + /* + * 'Time to make the doughnuts..' + */ + ap_MD5Init(&ctx); -API_EXPORT(char *) ap_MD5Encode(const char *password, const char * salt) { -/* salt has size 2, md5 hash size 22, plus 1 for trailing NUL, plus 4 for - '$' separators between md5 distinguisher, salt, and password.*/ - - static unsigned char ret[2+22+1+4]; - AP_MD5_CTX my_md5; - unsigned char hash[16], *cp; - register int i; - static const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"; - - /* - * Take the MD5 hash of the string argument. - */ - - sprintf(ret, "$1$%s$", salt); - - /* If the salt is shorter than 2, pad with random characters */ - for (cp = &ret[strlen(ret)]; cp < &ret[2]; ++cp) { - *cp = alphabet[rand() & 0x3F]; - } - ap_MD5Init(&my_md5); - ap_MD5Update(&my_md5, salt, 2); - ap_MD5Update(&my_md5, password, strlen(password)); - ap_MD5Final(hash, &my_md5); - - /* Take 3*8 bits (3 bytes) and store them as 4 base64 bytes (of 6 bit each) */ - /* Copy first 15 bytes in loop (producing 20 result bytes) */ - for (i = 0, cp = &ret[6]; i < 15; i += 3, cp += 4) { - long l = hash[i] | (hash[i+1] << 8) | (hash[i+2] << 16); - - cp[0] = alphabet[l&0x3F]; - cp[1] = alphabet[(l>>6)&0x3F]; - cp[2] = alphabet[(l>>12)&0x3F]; - cp[3] = alphabet[(l>>18)&0x3F]; - } - cp[0] = alphabet[hash[i]&0x3F]; /* Use 16th byte as 21st result byte */ - cp[1] = alphabet[(hash[i]>>6)&0x3F]; /* Last 2 bits of 16th byte are 22nd result byte */ - cp[2] = '\0'; - - /*ap_assert(&cp[2] == &ret[(sizeof ret)-1]);*/ - - return (char *)ret; + /* + * The password first, since that is what is most unknown + */ + ap_MD5Update(&ctx, pw, strlen(pw)); + + /* + * Then our magic string + */ + ap_MD5Update(&ctx, apr1_id, strlen(apr1_id)); + + /* + * Then the raw salt + */ + ap_MD5Update(&ctx, sp, sl); + + /* + * Then just as many characters of the MD5(pw, salt, pw) + */ + ap_MD5Init(&ctx1); + ap_MD5Update(&ctx1, pw, strlen(pw)); + ap_MD5Update(&ctx1, sp, sl); + ap_MD5Update(&ctx1, pw, strlen(pw)); + ap_MD5Final(final, &ctx1); + for(pl = strlen(pw); pl > 0; pl -= 16) { + ap_MD5Update(&ctx, final, (pl > 16) ? 16 : pl); + } + + /* + * Don't leave anything around in vm they could use. + */ + memset(final, 0, sizeof(final)); + + /* + * Then something really weird... + */ + for (i = strlen(pw); i != 0; i >>= 1) { + if (i & 1) { + ap_MD5Update(&ctx, final, 1); + } + else { + ap_MD5Update(&ctx, pw, 1); + } + } + + /* + * Now make the output string. We know our limitations, so we + * can use the string routines without bounds checking. + */ + strcpy(passwd, apr1_id); + strncat(passwd, sp, sl); + strcat(passwd, "$"); + + ap_MD5Final(final, &ctx); + + /* + * And now, just to make sure things don't run too fast.. + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for (i = 0; i < 1000; i++) { + ap_MD5Init(&ctx1); + if (i & 1) { + ap_MD5Update(&ctx1, pw, strlen(pw)); + } + else { + ap_MD5Update(&ctx1, final, 16); + } + if (i % 3) { + ap_MD5Update(&ctx1, sp, sl); + } + + if (i % 7) { + ap_MD5Update(&ctx1, pw, strlen(pw)); + } + + if (i & 1) { + ap_MD5Update(&ctx1, final, 16); + } + else { + ap_MD5Update(&ctx1, pw, strlen(pw)); + } + ap_MD5Final(final,&ctx1); + } + + p = passwd + strlen(passwd); + + l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4; + l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4; + l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4; + l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4; + l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4; + l = final[11] ; to64(p, l, 2); p += 2; + *p = '\0'; + + /* + * Don't leave anything around in vm they could use. + */ + memset(final, 0, sizeof(final)); + + ap_cpystrn(result, passwd, nbytes - 1); +} + +/* + * Validate a plaintext password against a smashed one. Use either + * crypt() (if available) or ap_MD5Encode(), depending upon the format + * of the smashed input password. Return NULL if they match, or + * an explanatory text string if they don't. + */ + +API_EXPORT(char *) ap_validate_password(const char *passwd, const char *hash) +{ + char sample[120]; + char *crypt_pw; + + if (!strncmp(hash, apr1_id, strlen(apr1_id))) { + /* + * The hash was created using our custom algorithm. + */ + ap_MD5Encode(passwd, hash, sample, sizeof(sample)); + } + else { + /* + * It's not our algorithm, so feed it to crypt() if possible. + */ +#ifdef WIN32 + return "crypt() unavailable on Win32, cannot validate password"; +#else + crypt_pw = crypt(passwd, hash); + ap_cpystrn(sample, crypt_pw, sizeof(sample) - 1); + } + return (strcmp(sample, hash) == 0) ? NULL : "password mismatch"; +#endif } 1.4 +3 -1 apache-1.3/src/include/ap_md5.h Index: ap_md5.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/ap_md5.h,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- ap_md5.h 1999/01/25 22:55:37 1.3 +++ ap_md5.h 1999/02/03 16:22:31 1.4 @@ -108,7 +108,9 @@ API_EXPORT(void) ap_MD5Update(AP_MD5_CTX * context, const unsigned char *input, unsigned int inputLen); API_EXPORT(void) ap_MD5Final(unsigned char digest[16], AP_MD5_CTX * context); -API_EXPORT(char *) ap_MD5Encode(const char *, const char *); +API_EXPORT(void) ap_MD5Encode(const char *password, const char *salt, + char *result, size_t nbytes); +API_EXPORT(char *) ap_validate_password(const char *passwd, const char *hash); #ifdef __cplusplus } 1.25 +5 -1 apache-1.3/src/include/ap_mmn.h Index: ap_mmn.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/ap_mmn.h,v retrieving revision 1.24 retrieving revision 1.25 diff -u -r1.24 -r1.25 --- ap_mmn.h 1999/01/29 14:28:55 1.24 +++ ap_mmn.h 1999/02/03 16:22:31 1.25 @@ -206,6 +206,10 @@ * 19990108-1 - add ap_find_opaque_token() for things like ETags * (1.3.5-dev) which can contain opaque quoted strings, and * ap_MD5Encode() for MD5 password handling. + * 19990108-2 - add ap_validate_password() and change ap_MD5Encode() + * (1.3.5-dev) to use a stronger algorithm (which is incompatible + * with the one introduced [but not released] with + * 19990108-1). */ #define MODULE_MAGIC_COOKIE 0x41503133UL /* "AP13" */ @@ -213,7 +217,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 19990108 #endif -#define MODULE_MAGIC_NUMBER_MINOR 1 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 2 /* 0...n */ #define MODULE_MAGIC_NUMBER MODULE_MAGIC_NUMBER_MAJOR /* backward compat */ /* Useful for testing for features. */ 1.45 +5 -14 apache-1.3/src/modules/standard/mod_auth.c Index: mod_auth.c =================================================================== RCS file: /home/cvs/apache-1.3/src/modules/standard/mod_auth.c,v retrieving revision 1.44 retrieving revision 1.45 diff -u -r1.44 -r1.45 --- mod_auth.c 1999/01/31 22:01:34 1.44 +++ mod_auth.c 1999/02/03 16:22:32 1.45 @@ -75,9 +75,6 @@ #include "http_log.h" #include "http_protocol.h" #include "ap_md5.h" -#if defined(HAVE_CRYPT_H) -#include <crypt.h> -#endif typedef struct auth_config_struct { char *auth_pwfile; @@ -204,6 +201,7 @@ conn_rec *c = r->connection; const char *sent_pw; char *real_pw; + char *invalid_pw; int res; if ((res = ap_get_basic_auth_pw(r, &sent_pw))) @@ -220,18 +218,11 @@ ap_note_basic_auth_failure(r); return AUTH_REQUIRED; } - if (real_pw[0] == '$' && real_pw[1] == '1') { - const char *salt = real_pw + 3; - salt = ap_getword(r->pool, &salt, '$'); - res = strcmp(real_pw, ap_MD5Encode(sent_pw, salt)); - } - else { - res = strcmp(real_pw, crypt(sent_pw, real_pw)); - } - - if (res != 0) { + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, - "user %s: password mismatch: %s", c->user, r->uri); + "user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); ap_note_basic_auth_failure(r); return AUTH_REQUIRED; } 1.40 +5 -14 apache-1.3/src/modules/standard/mod_auth_db.c Index: mod_auth_db.c =================================================================== RCS file: /home/cvs/apache-1.3/src/modules/standard/mod_auth_db.c,v retrieving revision 1.39 retrieving revision 1.40 diff -u -r1.39 -r1.40 --- mod_auth_db.c 1999/01/31 22:01:34 1.39 +++ mod_auth_db.c 1999/02/03 16:22:32 1.40 @@ -97,9 +97,6 @@ #include "http_protocol.h" #include <db.h> #include "ap_md5.h" -#if defined(HAVE_CRYPT_H) -#include <crypt.h> -#endif #if defined(DB_VERSION_MAJOR) && (DB_VERSION_MAJOR == 2) #define DB2 @@ -230,6 +227,7 @@ conn_rec *c = r->connection; const char *sent_pw; char *real_pw, *colon_pw; + char *invalid_pw; int res; if ((res = ap_get_basic_auth_pw(r, &sent_pw))) @@ -250,19 +248,12 @@ colon_pw = strchr(real_pw, ':'); if (colon_pw) { *colon_pw = '\0'; - } - if (real_pw[0] == '$' && real_pw[1] == '1') { - char *salt = real_pw + 3; - salt = ap_getword(r->pool, &salt, '$'); - res = strcmp(real_pw, ap_MD5Encode(sent_pw, salt)); } - else { - res = strcmp(real_pw, crypt(sent_pw, real_pw)); - } - - if (res != 0) { + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, - "DB user %s: password mismatch: %s", c->user, r->uri); + "DB user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); ap_note_basic_auth_failure(r); return AUTH_REQUIRED; } 1.45 +6 -14 apache-1.3/src/modules/standard/mod_auth_dbm.c Index: mod_auth_dbm.c =================================================================== RCS file: /home/cvs/apache-1.3/src/modules/standard/mod_auth_dbm.c,v retrieving revision 1.44 retrieving revision 1.45 diff -u -r1.44 -r1.45 --- mod_auth_dbm.c 1999/01/31 22:01:34 1.44 +++ mod_auth_dbm.c 1999/02/03 16:22:32 1.45 @@ -76,9 +76,6 @@ #include "http_protocol.h" #include <ndbm.h> #include "ap_md5.h" -#if defined(HAVE_CRYPT_H) -#include <crypt.h> -#endif /* * Module definition information - the part between the -START and -END @@ -212,6 +209,7 @@ conn_rec *c = r->connection; const char *sent_pw; char *real_pw, *colon_pw; + char *invalid_pw; int res; if ((res = ap_get_basic_auth_pw(r, &sent_pw))) @@ -230,20 +228,14 @@ } /* Password is up to first : if exists */ colon_pw = strchr(real_pw, ':'); - if (colon_pw) + if (colon_pw) { *colon_pw = '\0'; - if (real_pw[0] == '$' && real_pw[1] == '1') { - char *salt = real_pw + 3; - salt = ap_getword(r->pool, &salt, '$'); - res = strcmp(real_pw, ap_MD5Encode(sent_pw, salt)); - } - else { - res = strcmp(real_pw, crypt(sent_pw, real_pw)); } - - if (res != 0) { + invalid_pw = ap_validate_password(sent_pw, real_pw); + if (invalid_pw != NULL) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, - "user %s: password mismatch: %s", c->user, r->uri); + "DBM user %s: authentication failure for \"%s\": %s", + c->user, r->uri, invalid_pw); ap_note_basic_auth_failure(r); return AUTH_REQUIRED; } 1.20 +6 -5 apache-1.3/src/support/htpasswd.c Index: htpasswd.c =================================================================== RCS file: /home/cvs/apache-1.3/src/support/htpasswd.c,v retrieving revision 1.19 retrieving revision 1.20 diff -u -r1.19 -r1.20 --- htpasswd.c 1999/01/25 22:55:41 1.19 +++ htpasswd.c 1999/02/03 16:22:34 1.20 @@ -13,6 +13,7 @@ #include "ap_config.h" #include <sys/types.h> #include <signal.h> +#include "ap.h" #include "ap_md5.h" #ifdef WIN32 @@ -159,7 +160,7 @@ static void add_password(char *user, FILE *f, int use_md5) { - char *pw, *cpw, salt[3]; + char *pw, cpw[120], salt[9]; pw = strd((char *) getpass("New password:")); if (strcmp(pw, (char *) getpass("Re-type new password:"))) { @@ -170,14 +171,14 @@ exit(1); } (void) srand((int) time((time_t *) NULL)); - to64(&salt[0], rand(), 2); - salt[2] = '\0'; + to64(&salt[0], rand(), 8); + salt[8] = '\0'; if (use_md5) { - cpw = (char *)ap_MD5Encode(pw, salt); + ap_MD5Encode(pw, salt, cpw, sizeof(cpw)); } else { - cpw = (char *)crypt(pw, salt); + ap_cpystrn(cpw, (char *)crypt(pw, salt), sizeof(cpw) - 1); } free(pw); fprintf(f, "%s:%s\n", user, cpw); 1.13 +1 -0 apache-1.3/src/support/httpd.exp Index: httpd.exp =================================================================== RCS file: /home/cvs/apache-1.3/src/support/httpd.exp,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- httpd.exp 1999/01/29 14:28:56 1.12 +++ httpd.exp 1999/02/03 16:22:34 1.13 @@ -352,6 +352,7 @@ ap_util_init ap_util_uri_init ap_uudecode +ap_validate_password ap_vbprintf ap_vformatter ap_vsnprintf