summaryrefslogtreecommitdiff
path: root/src/mod_authn_file.c
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2021-06-10 08:27:20 -0400
committerGlenn Strauss <gstrauss@gluelogic.com>2021-08-27 02:16:53 -0400
commitc987bec18e0661b5a99d7ea2f8c54a08e7776ff6 (patch)
tree9d33d9dabfe087c5b0325c70e650f32be76d3ba1 /src/mod_authn_file.c
parent396a92306648266e1df60d1d12d07b7f4d01b9d9 (diff)
downloadlighttpd-git-c987bec18e0661b5a99d7ea2f8c54a08e7776ff6.tar.gz
[multiple] use <algo>_iov() digest funcs
reduce code duplication make it easier to add new algos mod_authn_file: - leverage r->tmp_buf instead of temporary allocating buffer_init() - mod_authn_file_htpasswd_basic() - compare binary SHA1 (shorter) rather than base64 (longer) - split crypt() from mod_authn_file_htpasswd_basic() to separate func - apr_md5_encode() modifications for slightly better performance
Diffstat (limited to 'src/mod_authn_file.c')
-rw-r--r--src/mod_authn_file.c341
1 files changed, 151 insertions, 190 deletions
diff --git a/src/mod_authn_file.c b/src/mod_authn_file.c
index 5a7e78bf..c1602d8d 100644
--- a/src/mod_authn_file.c
+++ b/src/mod_authn_file.c
@@ -166,57 +166,27 @@ SETDEFAULTS_FUNC(mod_authn_file_set_defaults) {
-#ifdef USE_LIB_CRYPTO
-
-static void mod_authn_file_digest_sha256(http_auth_info_t *ai, const char *pw, size_t pwlen) {
- SHA256_CTX ctx;
- SHA256_Init(&ctx);
- SHA256_Update(&ctx, (const unsigned char *)ai->username, ai->ulen);
- SHA256_Update(&ctx, CONST_STR_LEN(":"));
- SHA256_Update(&ctx, (const unsigned char *)ai->realm, ai->rlen);
- SHA256_Update(&ctx, CONST_STR_LEN(":"));
- SHA256_Update(&ctx, (const unsigned char *)pw, pwlen);
- SHA256_Final(ai->digest, &ctx);
-}
-
-#ifdef USE_LIB_CRYPTO_SHA512_256
-static void mod_authn_file_digest_sha512_256(http_auth_info_t *ai, const char *pw, size_t pwlen) {
- SHA512_CTX ctx;
- SHA512_256_Init(&ctx);
- SHA512_256_Update(&ctx, (const unsigned char *)ai->username, ai->ulen);
- SHA512_256_Update(&ctx, CONST_STR_LEN(":"));
- SHA512_256_Update(&ctx, (const unsigned char *)ai->realm, ai->rlen);
- SHA512_256_Update(&ctx, CONST_STR_LEN(":"));
- SHA512_256_Update(&ctx, (const unsigned char *)pw, pwlen);
- SHA512_256_Final(ai->digest, &ctx);
-}
-#endif
-
-#endif
-
-static void mod_authn_file_digest_md5(http_auth_info_t *ai, const char *pw, size_t pwlen) {
- li_MD5_CTX ctx;
- li_MD5_Init(&ctx);
- li_MD5_Update(&ctx, (const unsigned char *)ai->username, ai->ulen);
- li_MD5_Update(&ctx, CONST_STR_LEN(":"));
- li_MD5_Update(&ctx, (const unsigned char *)ai->realm, ai->rlen);
- li_MD5_Update(&ctx, CONST_STR_LEN(":"));
- li_MD5_Update(&ctx, (const unsigned char *)pw, pwlen);
- li_MD5_Final(ai->digest, &ctx);
-}
-
static void mod_authn_file_digest(http_auth_info_t *ai, const char *pw, size_t pwlen) {
- if (ai->dalgo & HTTP_AUTH_DIGEST_MD5)
- mod_authn_file_digest_md5(ai, pw, pwlen);
+ li_md_iov_fn digest_iov = MD5_iov;
+ /* (ai->dalgo & HTTP_AUTH_DIGEST_MD5) default */
#ifdef USE_LIB_CRYPTO
- else if (ai->dalgo & HTTP_AUTH_DIGEST_SHA256)
- mod_authn_file_digest_sha256(ai, pw, pwlen);
+ if (ai->dalgo & HTTP_AUTH_DIGEST_SHA256)
+ digest_iov = SHA256_iov;
#ifdef USE_LIB_CRYPTO_SHA512_256
else if (ai->dalgo & HTTP_AUTH_DIGEST_SHA512_256)
- mod_authn_file_digest_sha512_256(ai, pw, pwlen);
+ digest_iov = SHA512_256_iov;
#endif
#endif
+
+ struct const_iovec iov[] = {
+ { ai->username, ai->ulen }
+ ,{ ":", 1 }
+ ,{ ai->realm, ai->rlen }
+ ,{ ":", 1 }
+ ,{ pw, pwlen }
+ };
+ digest_iov(ai->digest, iov, sizeof(iov)/sizeof(*iov));
}
@@ -390,32 +360,34 @@ static int mod_authn_file_htpasswd_get(const buffer *auth_fn, const char *userna
static handler_t mod_authn_file_plain_digest(request_st * const r, void *p_d, http_auth_info_t * const ai) {
plugin_data *p = (plugin_data *)p_d;
- buffer *password_buf = buffer_init();/* password-string from auth-backend */
- int rc;
mod_authn_file_patch_config(r, p);
- rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile, ai->username, ai->ulen, password_buf, r->conf.errh);
- if (0 == rc) {
- /* generate password from plain-text */
- mod_authn_file_digest(ai, BUF_PTR_LEN(password_buf));
- }
- ck_memzero(password_buf->ptr, password_buf->size);
- buffer_free(password_buf);
- return (0 == rc) ? HANDLER_GO_ON : HANDLER_ERROR;
+ buffer * const tb = r->tmp_buf; /* password-string from auth-backend */
+ int rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile,
+ ai->username, ai->ulen, tb,
+ r->conf.errh);
+ if (0 != rc) return HANDLER_ERROR;
+
+ /* generate password digest from plain-text */
+ mod_authn_file_digest(ai, BUF_PTR_LEN(tb));
+ size_t tblen = (buffer_clen(tb) + 63) & ~63u;
+ buffer_clear(tb);
+ ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size);
+ return HANDLER_GO_ON;
}
static handler_t mod_authn_file_plain_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) {
plugin_data *p = (plugin_data *)p_d;
- buffer *password_buf = buffer_init();/* password-string from auth-backend */
- int rc;
mod_authn_file_patch_config(r, p);
- rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile, BUF_PTR_LEN(username), password_buf, r->conf.errh);
+ buffer * const tb = r->tmp_buf; /* password-string from auth-backend */
+ int rc = mod_authn_file_htpasswd_get(p->conf.auth_plain_userfile,
+ BUF_PTR_LEN(username), tb,
+ r->conf.errh);
if (0 == rc) {
- rc = ck_memeq_const_time(BUF_PTR_LEN(password_buf), pw, strlen(pw))
- ? 0
- : -1;
+ rc = ck_memeq_const_time(BUF_PTR_LEN(tb), pw, strlen(pw)) ? 0 : -1;
+ size_t tblen = (buffer_clen(tb) + 63) & ~63u;
+ buffer_clear(tb);
+ ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size);
}
- ck_memzero(password_buf->ptr, password_buf->size);
- buffer_free(password_buf);
return 0 == rc && http_auth_match_rules(require, username->ptr, NULL, NULL)
? HANDLER_GO_ON
: HANDLER_ERROR;
@@ -426,6 +398,7 @@ static handler_t mod_authn_file_plain_basic(request_st * const r, void *p_d, con
/**
* the $apr1$ handling is taken from apache 1.3.x
+ * XXX: code has since been modified for slightly better performance
*/
/*
@@ -459,201 +432,151 @@ static void to64(char *s, unsigned long v, int n)
}
}
-static void apr_md5_encode(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.
- */
+static size_t apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
+ force_assert(nbytes >= 37); /*(nbytes should be >= 37)*/
- char passwd[120], *p;
- const char *sp, *ep;
- unsigned char final[APR_MD5_DIGESTSIZE];
- ssize_t sl, pl, i;
- li_MD5_CTX ctx, ctx1;
- unsigned long l;
+ const size_t pwlen = strlen(pw);
+ ssize_t sl;
/*
* 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 0 /*(already checked and stepped-over in caller)*/
/*
* If it starts with the magic string, then skip that.
*/
- if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
- sp += strlen(APR1_ID);
+ if (!strncmp(salt, APR1_ID, sizeof(APR1_ID)-1)) {
+ salt += sizeof(APR1_ID)-1;
}
+ #endif
/*
- * It stops at the first '$' or 8 chars, whichever comes first
+ * Get the length of the true salt
*/
- for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
- continue;
- }
-
/*
- * Get the length of the true salt
+ * It stops at the first '$' or 8 chars, whichever comes first
*/
- sl = ep - sp;
+ for (sl = 0; sl < 8 && salt[sl] != '$' && salt[sl] != '\0'; ++sl) ;
+
+ /* result begins with "$apr1$salt$" */
+ memcpy(result, APR1_ID, sizeof(APR1_ID)-1);
+ memcpy(result+sizeof(APR1_ID)-1, salt, sl);
+ result[sizeof(APR1_ID)-1+sl] = '$';
+
+ MD5_CTX ctx;
+ unsigned char final[APR_MD5_DIGESTSIZE];
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, pw, pwlen);
+ MD5_Update(&ctx, salt, sl);
+ MD5_Update(&ctx, pw, pwlen);
+ MD5_Final(final, &ctx);
/*
* 'Time to make the doughnuts..'
*/
- li_MD5_Init(&ctx);
+ MD5_Init(&ctx);
/*
* The password first, since that is what is most unknown
*/
- li_MD5_Update(&ctx, pw, strlen(pw));
+ MD5_Update(&ctx, pw, pwlen);
+ #if 0
/*
* Then our magic string
*/
- li_MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
+ MD5_Update(&ctx, APR1_ID, sizeof(APR1_ID)-1);
/*
* Then the raw salt
*/
- li_MD5_Update(&ctx, sp, sl);
+ MD5_Update(&ctx, salt, sl);
+ #else
+ MD5_Update(&ctx, result, sizeof(APR1_ID)-1 + sl);
+ #endif
/*
* Then just as many characters of the MD5(pw, salt, pw)
*/
- li_MD5_Init(&ctx1);
- li_MD5_Update(&ctx1, pw, strlen(pw));
- li_MD5_Update(&ctx1, sp, sl);
- li_MD5_Update(&ctx1, pw, strlen(pw));
- li_MD5_Final(final, &ctx1);
- for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) {
- li_MD5_Update(&ctx, final,
- (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl);
+ for (ssize_t pl = pwlen; pl > 0; pl -= APR_MD5_DIGESTSIZE) {
+ MD5_Update(&ctx, final,
+ (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl);
}
/*
* Don't leave anything around in vm they could use.
*/
- memset(final, 0, sizeof(final));
+ /*ck_memzero(final, sizeof(final));*/
+ final[0] = 0; /*(preserve behavior for loop below)*/
/*
* Then something really weird...
*/
- for (i = strlen(pw); i != 0; i >>= 1) {
- if (i & 1) {
- li_MD5_Update(&ctx, final, 1);
- }
- else {
- li_MD5_Update(&ctx, pw, 1);
- }
+ for (size_t i = pwlen; i != 0; i >>= 1) {
+ MD5_Update(&ctx, (i & 1) ? (char *)final : 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, "$");
-
- li_MD5_Final(final, &ctx);
+ MD5_Final(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++) {
- li_MD5_Init(&ctx1);
+ for (int i = 0; i < 1000; ++i) {
+ MD5_Init(&ctx);
if (i & 1) {
- li_MD5_Update(&ctx1, pw, strlen(pw));
+ MD5_Update(&ctx, pw, pwlen);
}
else {
- li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
+ MD5_Update(&ctx, final, APR_MD5_DIGESTSIZE);
}
if (i % 3) {
- li_MD5_Update(&ctx1, sp, sl);
+ MD5_Update(&ctx, salt, sl);
}
if (i % 7) {
- li_MD5_Update(&ctx1, pw, strlen(pw));
+ MD5_Update(&ctx, pw, pwlen);
}
if (i & 1) {
- li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
+ MD5_Update(&ctx, final, APR_MD5_DIGESTSIZE);
}
else {
- li_MD5_Update(&ctx1, pw, strlen(pw));
+ MD5_Update(&ctx, pw, pwlen);
}
- li_MD5_Final(final,&ctx1);
+ MD5_Final(final,&ctx);
}
- p = passwd + strlen(passwd);
+ /*
+ * Now make the output string. (nbytes checked at top of func)
+ * Maximum result size below is 37:
+ * 6 for APR_ID, <= 8 for salt, 1 for '$', 22 for password hash
+ */
- 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';
+ result += sizeof(APR1_ID)-1 + sl + 1;
+ to64(result, (final[ 0]<<16) | (final[ 6]<<8) | final[12], 4);
+ to64(result+4, (final[ 1]<<16) | (final[ 7]<<8) | final[13], 4);
+ to64(result+8, (final[ 2]<<16) | (final[ 8]<<8) | final[14], 4);
+ to64(result+12, (final[ 3]<<16) | (final[ 9]<<8) | final[15], 4);
+ to64(result+16, (final[ 4]<<16) | (final[10]<<8) | final[ 5], 4);
+ to64(result+20, final[11] , 2);
/*
* Don't leave anything around in vm they could use.
*/
ck_memzero(final, sizeof(final));
-
- /* FIXME
- */
-#define apr_cpystrn strncpy
- apr_cpystrn(result, passwd, nbytes - 1);
-}
-
-static void apr_sha_encode(const char *pw, char *result, size_t nbytes) {
- unsigned char digest[20];
- size_t base64_written;
- SHA_CTX sha1;
-
- SHA1_Init(&sha1);
- SHA1_Update(&sha1, (const unsigned char *) pw, strlen(pw));
- SHA1_Final(digest, &sha1);
-
- memset(result, 0, nbytes);
-
- /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */
- if (nbytes < 5 + 28 + 1) return;
-
- memcpy(result, "{SHA}", 5);
- base64_written = li_to_base64(result + 5, nbytes - 5, digest, 20, BASE64_STANDARD);
- force_assert(base64_written == 28);
- result[5 + base64_written] = '\0'; /* terminate string */
+ return (sizeof(APR1_ID)-1 + sl + 1 + 22);
}
-static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) {
- plugin_data *p = (plugin_data *)p_d;
- buffer *password = buffer_init();/* password-string from auth-backend */
- int rc;
- mod_authn_file_patch_config(r, p);
- rc = mod_authn_file_htpasswd_get(p->conf.auth_htpasswd_userfile, BUF_PTR_LEN(username), password, r->conf.errh);
- if (0 == rc) {
- char sample[256];
- rc = -1;
- if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
- /*
- * The hash was created using $apr1$ custom algorithm.
- */
- apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
- rc = strcmp(sample, password->ptr);
- }
- else if (0 == strncmp(password->ptr, "{SHA}", 5)) {
- apr_sha_encode(pw, sample, sizeof(sample));
- rc = strcmp(sample, password->ptr);
- }
- #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
- /* a simple DES password is 2 + 11 characters. everything else should be longer. */
- else if (buffer_clen(password) >= 13) {
+#if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
+static int mod_authn_file_crypt_cmp(const buffer * const password, const char * const pw) {
+ int rc = -1;
char *crypted;
+ char sample[256];
#if 0 && defined(HAVE_CRYPT_R)
struct crypt_data crypt_tmp_data;
#ifdef _AIX
@@ -663,9 +586,10 @@ static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d,
#endif
#endif
#ifdef USE_LIB_CRYPTO_MD4 /*(for MD4_*() (e.g. MD4_Update()))*/
+ /*(caller checked buffer_clen(passwd) >= 13)*/
if (0 == memcmp(password->ptr, CONST_STR_LEN("$1+ntlm$"))) {
/* CRYPT-MD5-NTLM algorithm
- * This algorithm allows for the construction of (slight more)
+ * This algorithm allows for the construction of (slightly more)
* secure, salted password hashes from an environment where only
* legacy NTLM hashes are available and where it is not feasible
* to re-hash all the passwords with the MD5-based crypt(). */
@@ -681,10 +605,6 @@ static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d,
&& pwlen < sizeof(sample)) {
/* compute NTLM hash and convert to lowercase hex chars
* (require lc hex chars from li_tohex()) */
- char ntlmhash[16];
- char ntlmhex[33]; /*(sizeof(ntlmhash)*2 + 1)*/
- MD4_CTX c;
- MD4_Init(&c);
if (pwlen) {
/*(reuse sample buffer to encode pw into UCS-2LE)
*(Note: assumes pw input in ISO-8859-1) */
@@ -693,9 +613,10 @@ static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d,
sample[i] = pw[(i >> 1)];
sample[i+1] = 0;
}
- MD4_Update(&c, (unsigned char *)sample, pwlen);
}
- MD4_Final((unsigned char *)ntlmhash, &c);
+ char ntlmhash[MD4_DIGEST_LENGTH];
+ char ntlmhex[MD4_DIGEST_LENGTH*2+1];
+ MD4_once((unsigned char *)ntlmhash, sample, pwlen);
li_tohex(ntlmhex,sizeof(ntlmhex),ntlmhash,sizeof(ntlmhash));
/*(reuse sample buffer for salt (FYI: expect slen == 8))*/
@@ -725,11 +646,51 @@ static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d,
rc = strcmp(password->ptr, crypted);
}
}
- }
- #endif
+ ck_memzero(sample, sizeof(sample));
+ return rc;
+}
+#endif
+
+static handler_t mod_authn_file_htpasswd_basic(request_st * const r, void *p_d, const http_auth_require_t * const require, const buffer * const username, const char * const pw) {
+ plugin_data *p = (plugin_data *)p_d;
+ mod_authn_file_patch_config(r, p);
+ buffer * const tb = r->tmp_buf; /* password-string from auth-backend */
+ int rc = mod_authn_file_htpasswd_get(p->conf.auth_htpasswd_userfile,
+ BUF_PTR_LEN(username), tb,
+ r->conf.errh);
+ if (0 != rc) return HANDLER_ERROR;
+
+ uint32_t tblen = buffer_clen(tb);
+ rc = -1;
+ if (tblen >= 5 && 0 == memcmp(tb->ptr, "{SHA}", 5)) {
+ /* 32 == (5 for "{SHA}" + 28 for base64 of SHA1 (20 bytes)) */
+ unsigned char digest[SHA_DIGEST_LENGTH*2];
+ SHA1_once(digest+SHA_DIGEST_LENGTH, pw, strlen(pw));
+ rc = SHA_DIGEST_LENGTH
+ == li_base64_dec(digest, sizeof(digest),
+ tb->ptr+5, tblen-5, BASE64_STANDARD)
+ && ck_memeq_const_time_fixed_len(digest, digest+SHA_DIGEST_LENGTH,
+ SHA_DIGEST_LENGTH);
+ rc = !rc; /* (0 == rc) for match */
+ ck_memzero(digest, sizeof(digest));
}
- ck_memzero(password->ptr, password->size);
- buffer_free(password);
+ else if (tblen >= 6 && 0 == memcmp(tb->ptr, "$apr1$", 6)) {
+ char sample[40]; /*(see comments at end of apr_md5_encode())*/
+ rc = tblen == apr_md5_encode(pw, tb->ptr+6, sample, sizeof(sample))
+ && ck_memeq_const_time_fixed_len(sample, tb->ptr, tblen);
+ rc = !rc; /* (0 == rc) for match */
+ ck_memzero(sample, sizeof(sample));
+ }
+ #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
+ /* simple DES password is 2 + 11 characters;
+ * everything else should be longer */
+ else if (tblen >= 13) {
+ rc = mod_authn_file_crypt_cmp(tb, pw);
+ }
+ #endif
+ tblen = (tblen + 63) & ~63u;
+ buffer_clear(tb);
+ ck_memzero(tb->ptr, tblen < tb->size ? tblen : tb->size);
return 0 == rc && http_auth_match_rules(require, username->ptr, NULL, NULL)
? HANDLER_GO_ON
: HANDLER_ERROR;