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