Files
2021-05-12 09:58:46 +00:00

265 lines
6.6 KiB
C

/* $Id$ */
/*
* Copyright (c) 2015--2018 Kristaps Dzonsons <kristaps@bsd.lv>
* Copyright (c) 2018 Charles Collicutt <charles@collicutt.co.uk>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <inttypes.h>
#if HAVE_MD5
# include <sys/types.h>
# include <md5.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "kcgi.h"
#include "extern.h"
#define MD5Updatec(_ctx, _b, _sz) \
MD5Update((_ctx), (const uint8_t *)(_b), (_sz))
static const char b64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static size_t
base64len(size_t len)
{
return((len + 2) / 3 * 4) + 1;
}
static size_t
base64buf(char *enc, const char *str, size_t len)
{
size_t i;
char *p;
p = enc;
for (i = 0; i < len - 2; i += 3) {
*p++ = b64[(str[i] >> 2) & 0x3F];
*p++ = b64[((str[i] & 0x3) << 4) |
((int)(str[i + 1] & 0xF0) >> 4)];
*p++ = b64[((str[i + 1] & 0xF) << 2) |
((int)(str[i + 2] & 0xC0) >> 6)];
*p++ = b64[str[i + 2] & 0x3F];
}
if (i < len) {
*p++ = b64[(str[i] >> 2) & 0x3F];
if (i == (len - 1)) {
*p++ = b64[((str[i] & 0x3) << 4)];
*p++ = '=';
} else {
*p++ = b64[((str[i] & 0x3) << 4) |
((int)(str[i + 1] & 0xF0) >> 4)];
*p++ = b64[((str[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
*p++ = '\0';
return (p - enc);
}
int
khttpbasic_validate(const struct kreq *req,
const char *user, const char *pass)
{
char *buf, *enc;
size_t sz;
int rc;
if (req->rawauth.type != KAUTH_BASIC &&
req->rawauth.type != KAUTH_BEARER)
return (-1);
else if (req->method == KMETHOD__MAX)
return (-1);
else if (req->rawauth.authorised == 0)
return (-1);
/* Make sure we don't bail on memory allocation. */
sz = strlen(user) + 1 + strlen(pass) + 1;
if ((buf = kxmalloc(sz)) == NULL)
return (-1);
sz = snprintf(buf, sz, "%s:%s", user, pass);
if ((enc = kxmalloc(base64len(sz))) == NULL) {
free(buf);
return (-1);
}
base64buf(enc, buf, sz);
rc = strcmp(enc, req->rawauth.d.basic.response) == 0;
free(enc);
free(buf);
return rc;
}
int
khttpdigest_validatehash(const struct kreq *req, const char *skey4)
{
MD5_CTX ctx;
unsigned char ha1[MD5_DIGEST_LENGTH],
ha2[MD5_DIGEST_LENGTH],
ha3[MD5_DIGEST_LENGTH];
char skey1[MD5_DIGEST_LENGTH * 2 + 1],
skey2[MD5_DIGEST_LENGTH * 2 + 1],
skey3[MD5_DIGEST_LENGTH * 2 + 1],
skeyb[MD5_DIGEST_LENGTH * 2 + 1],
count[9];
size_t i;
const struct khttpdigest *auth;
/*
* Make sure we're a digest with all fields intact.
*/
if (KAUTH_DIGEST != req->rawauth.type)
return(-1);
else if (KMETHOD__MAX == req->method)
return(-1);
else if (0 == req->rawauth.authorised)
return(-1);
auth = &req->rawauth.d.digest;
/*
* MD5-sess hashes the nonce and client nonce as well as the
* existing hash (user/real/pass).
*/
if (KHTTPALG_MD5_SESS == auth->alg) {
MD5Init(&ctx);
MD5Updatec(&ctx, skey4, strlen(skey4));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->cnonce, strlen(auth->cnonce));
MD5Final(ha1, &ctx);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
snprintf(&skey1[i * 2], 3, "%02x", ha1[i]);
} else
strlcpy(skey1, skey4, sizeof(skey1));
/* Now start the "auth" hash sequence. */
MD5Init(&ctx);
MD5Updatec(&ctx, kmethods[req->method],
strlen(kmethods[req->method]));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->uri, strlen(auth->uri));
/*
* If we're requesting integrity authentication ("auth-int"),
* then we also bring in the hash of the message body.
*/
if (KHTTPQOP_AUTH_INT == auth->qop) {
/* This shouldn't happen... */
if (NULL == req->rawauth.digest)
return(-1);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
snprintf(&skeyb[i * 2], 3, "%02x",
(unsigned char)req->rawauth.digest[i]);
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, skeyb, MD5_DIGEST_LENGTH * 2);
}
MD5Final(ha2, &ctx);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
snprintf(&skey2[i * 2], 3, "%02x", ha2[i]);
if (KHTTPQOP_AUTH_INT == auth->qop ||
KHTTPQOP_AUTH == auth->qop) {
snprintf(count, sizeof(count), "%08" PRIx32, auth->count);
MD5Init(&ctx);
MD5Updatec(&ctx, skey1, MD5_DIGEST_LENGTH * 2);
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, count, strlen(count));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->cnonce, strlen(auth->cnonce));
MD5Updatec(&ctx, ":", 1);
if (KHTTPQOP_AUTH_INT == auth->qop)
MD5Updatec(&ctx, "auth-int", 8);
else
MD5Updatec(&ctx, "auth", 4);
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, skey2, MD5_DIGEST_LENGTH * 2);
MD5Final(ha3, &ctx);
} else {
MD5Init(&ctx);
MD5Updatec(&ctx, skey1, MD5_DIGEST_LENGTH * 2);
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, skey2, MD5_DIGEST_LENGTH * 2);
MD5Final(ha3, &ctx);
}
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
snprintf(&skey3[i * 2], 3, "%02x", ha3[i]);
return(0 == strcmp(auth->response, skey3));
}
int
khttpdigest_validate(const struct kreq *req, const char *pass)
{
MD5_CTX ctx;
unsigned char ha4[MD5_DIGEST_LENGTH];
char skey4[MD5_DIGEST_LENGTH * 2 + 1];
size_t i;
const struct khttpdigest *auth;
/*
* Make sure we're a digest with all fields intact.
*/
if (KAUTH_DIGEST != req->rawauth.type)
return(-1);
else if (KMETHOD__MAX == req->method)
return(-1);
else if (0 == req->rawauth.authorised)
return(-1);
auth = &req->rawauth.d.digest;
MD5Init(&ctx);
MD5Updatec(&ctx, auth->user, strlen(auth->user));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, auth->realm, strlen(auth->realm));
MD5Updatec(&ctx, ":", 1);
MD5Updatec(&ctx, pass, strlen(pass));
MD5Final(ha4, &ctx);
for (i = 0; i < MD5_DIGEST_LENGTH; i++)
snprintf(&skey4[i * 2], 3, "%02x", ha4[i]);
return(khttpdigest_validatehash(req, skey4));
}