diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2021-06-10 08:27:20 -0400 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2021-08-27 02:16:53 -0400 |
commit | c987bec18e0661b5a99d7ea2f8c54a08e7776ff6 (patch) | |
tree | 9d33d9dabfe087c5b0325c70e650f32be76d3ba1 /src/mod_authn_file.c | |
parent | 396a92306648266e1df60d1d12d07b7f4d01b9d9 (diff) | |
download | lighttpd-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.c | 341 |
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; |