1 /*	$Id$ */
2 /*
3  * Copyright (c) 2015--2018 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2018 Charles Collicutt <charles@collicutt.co.uk>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include "config.h"
19 
20 #include <inttypes.h>
21 #if HAVE_MD5
22 # include <sys/types.h>
23 # include <md5.h>
24 #endif
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include "kcgi.h"
32 #include "extern.h"
33 
34 #define MD5Updatec(_ctx, _b, _sz) \
35 	MD5Update((_ctx), (const uint8_t *)(_b), (_sz))
36 
37 static const char b64[] =
38 	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
39 	"abcdefghijklmnopqrstuvwxyz"
40 	"0123456789+/";
41 
42 static size_t
base64len(size_t len)43 base64len(size_t len)
44 {
45 
46 	return((len + 2) / 3 * 4) + 1;
47 }
48 
49 static size_t
base64buf(char * enc,const char * str,size_t len)50 base64buf(char *enc, const char *str, size_t len)
51 {
52 	size_t 	i;
53 	char 	*p;
54 
55 	p = enc;
56 
57 	for (i = 0; i < len - 2; i += 3) {
58 		*p++ = b64[(str[i] >> 2) & 0x3F];
59 		*p++ = b64[((str[i] & 0x3) << 4) |
60 			((int)(str[i + 1] & 0xF0) >> 4)];
61 		*p++ = b64[((str[i + 1] & 0xF) << 2) |
62 			((int)(str[i + 2] & 0xC0) >> 6)];
63 		*p++ = b64[str[i + 2] & 0x3F];
64 	}
65 
66 	if (i < len) {
67 		*p++ = b64[(str[i] >> 2) & 0x3F];
68 		if (i == (len - 1)) {
69 			*p++ = b64[((str[i] & 0x3) << 4)];
70 			*p++ = '=';
71 		} else {
72 			*p++ = b64[((str[i] & 0x3) << 4) |
73 				((int)(str[i + 1] & 0xF0) >> 4)];
74 			*p++ = b64[((str[i + 1] & 0xF) << 2)];
75 		}
76 		*p++ = '=';
77 	}
78 
79 	*p++ = '\0';
80 	return (p - enc);
81 }
82 
83 int
khttpbasic_validate(const struct kreq * req,const char * user,const char * pass)84 khttpbasic_validate(const struct kreq *req,
85 	const char *user, const char *pass)
86 {
87 	char	*buf, *enc;
88 	size_t	 sz;
89 	int	 rc;
90 
91 	if (req->rawauth.type != KAUTH_BASIC &&
92 	    req->rawauth.type != KAUTH_BEARER)
93 		return (-1);
94 	else if (req->method == KMETHOD__MAX)
95 		return (-1);
96 	else if (req->rawauth.authorised == 0)
97 		return (-1);
98 
99 	/* Make sure we don't bail on memory allocation. */
100 
101 	sz = strlen(user) + 1 + strlen(pass) + 1;
102 	if ((buf = kxmalloc(sz)) == NULL)
103 		return (-1);
104 
105 	sz = snprintf(buf, sz, "%s:%s", user, pass);
106 	if ((enc = kxmalloc(base64len(sz))) == NULL) {
107 		free(buf);
108 		return (-1);
109 	}
110 
111 	base64buf(enc, buf, sz);
112 	rc = strcmp(enc, req->rawauth.d.basic.response) == 0;
113 
114 	free(enc);
115 	free(buf);
116 	return rc;
117 }
118 
119 int
khttpdigest_validatehash(const struct kreq * req,const char * skey4)120 khttpdigest_validatehash(const struct kreq *req, const char *skey4)
121 {
122 	MD5_CTX	 	 ctx;
123 	unsigned char	 ha1[MD5_DIGEST_LENGTH],
124 			 ha2[MD5_DIGEST_LENGTH],
125 			 ha3[MD5_DIGEST_LENGTH];
126 	char		 skey1[MD5_DIGEST_LENGTH * 2 + 1],
127 			 skey2[MD5_DIGEST_LENGTH * 2 + 1],
128 			 skey3[MD5_DIGEST_LENGTH * 2 + 1],
129 	                 skeyb[MD5_DIGEST_LENGTH * 2 + 1],
130 			 count[9];
131 	size_t		 i;
132 	const struct khttpdigest *auth;
133 
134 	/*
135 	 * Make sure we're a digest with all fields intact.
136 	 */
137 	if (KAUTH_DIGEST != req->rawauth.type)
138 		return(-1);
139 	else if (KMETHOD__MAX == req->method)
140 		return(-1);
141 	else if (0 == req->rawauth.authorised)
142 		return(-1);
143 
144 	auth = &req->rawauth.d.digest;
145 
146 	/*
147 	 * MD5-sess hashes the nonce and client nonce as well as the
148 	 * existing hash (user/real/pass).
149 	 */
150 
151 	if (KHTTPALG_MD5_SESS == auth->alg) {
152 		MD5Init(&ctx);
153 		MD5Updatec(&ctx, skey4, strlen(skey4));
154 		MD5Updatec(&ctx, ":", 1);
155 		MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
156 		MD5Updatec(&ctx, ":", 1);
157 		MD5Updatec(&ctx, auth->cnonce, strlen(auth->cnonce));
158 		MD5Final(ha1, &ctx);
159 		for (i = 0; i < MD5_DIGEST_LENGTH; i++)
160 			snprintf(&skey1[i * 2], 3, "%02x", ha1[i]);
161 	} else
162 		strlcpy(skey1, skey4, sizeof(skey1));
163 
164 	/* Now start the "auth" hash sequence. */
165 
166 	MD5Init(&ctx);
167 	MD5Updatec(&ctx, kmethods[req->method],
168 		strlen(kmethods[req->method]));
169 	MD5Updatec(&ctx, ":", 1);
170 	MD5Updatec(&ctx, auth->uri, strlen(auth->uri));
171 
172 	/*
173 	 * If we're requesting integrity authentication ("auth-int"),
174 	 * then we also bring in the hash of the message body.
175 	 */
176 
177 	if (KHTTPQOP_AUTH_INT == auth->qop) {
178 		/* This shouldn't happen... */
179 		if (NULL == req->rawauth.digest)
180 			return(-1);
181 
182 		for (i = 0; i < MD5_DIGEST_LENGTH; i++)
183 			snprintf(&skeyb[i * 2], 3, "%02x",
184 			    (unsigned char)req->rawauth.digest[i]);
185 
186 		MD5Updatec(&ctx, ":", 1);
187 		MD5Updatec(&ctx, skeyb, MD5_DIGEST_LENGTH * 2);
188 	}
189 
190 	MD5Final(ha2, &ctx);
191 
192 	for (i = 0; i < MD5_DIGEST_LENGTH; i++)
193 		snprintf(&skey2[i * 2], 3, "%02x", ha2[i]);
194 
195 	if (KHTTPQOP_AUTH_INT == auth->qop ||
196 	    KHTTPQOP_AUTH == auth->qop) {
197 		snprintf(count, sizeof(count), "%08" PRIx32, auth->count);
198 		MD5Init(&ctx);
199 		MD5Updatec(&ctx, skey1, MD5_DIGEST_LENGTH * 2);
200 		MD5Updatec(&ctx, ":", 1);
201 		MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
202 		MD5Updatec(&ctx, ":", 1);
203 		MD5Updatec(&ctx, count, strlen(count));
204 		MD5Updatec(&ctx, ":", 1);
205 		MD5Updatec(&ctx, auth->cnonce, strlen(auth->cnonce));
206 		MD5Updatec(&ctx, ":", 1);
207 		if (KHTTPQOP_AUTH_INT == auth->qop)
208 			MD5Updatec(&ctx, "auth-int", 8);
209 		else
210 			MD5Updatec(&ctx, "auth", 4);
211 		MD5Updatec(&ctx, ":", 1);
212 		MD5Updatec(&ctx, skey2, MD5_DIGEST_LENGTH * 2);
213 		MD5Final(ha3, &ctx);
214 	} else {
215 		MD5Init(&ctx);
216 		MD5Updatec(&ctx, skey1, MD5_DIGEST_LENGTH * 2);
217 		MD5Updatec(&ctx, ":", 1);
218 		MD5Updatec(&ctx, auth->nonce, strlen(auth->nonce));
219 		MD5Updatec(&ctx, ":", 1);
220 		MD5Updatec(&ctx, skey2, MD5_DIGEST_LENGTH * 2);
221 		MD5Final(ha3, &ctx);
222 	}
223 
224 	for (i = 0; i < MD5_DIGEST_LENGTH; i++)
225 		snprintf(&skey3[i * 2], 3, "%02x", ha3[i]);
226 
227 	return(0 == strcmp(auth->response, skey3));
228 }
229 
230 int
khttpdigest_validate(const struct kreq * req,const char * pass)231 khttpdigest_validate(const struct kreq *req, const char *pass)
232 {
233 	MD5_CTX	 	 ctx;
234 	unsigned char	 ha4[MD5_DIGEST_LENGTH];
235 	char		 skey4[MD5_DIGEST_LENGTH * 2 + 1];
236 	size_t		 i;
237 	const struct khttpdigest *auth;
238 
239 	/*
240 	 * Make sure we're a digest with all fields intact.
241 	 */
242 
243 	if (KAUTH_DIGEST != req->rawauth.type)
244 		return(-1);
245 	else if (KMETHOD__MAX == req->method)
246 		return(-1);
247 	else if (0 == req->rawauth.authorised)
248 		return(-1);
249 
250 	auth = &req->rawauth.d.digest;
251 
252 	MD5Init(&ctx);
253 	MD5Updatec(&ctx, auth->user, strlen(auth->user));
254 	MD5Updatec(&ctx, ":", 1);
255 	MD5Updatec(&ctx, auth->realm, strlen(auth->realm));
256 	MD5Updatec(&ctx, ":", 1);
257 	MD5Updatec(&ctx, pass, strlen(pass));
258 	MD5Final(ha4, &ctx);
259 
260 	for (i = 0; i < MD5_DIGEST_LENGTH; i++)
261 		snprintf(&skey4[i * 2], 3, "%02x", ha4[i]);
262 
263 	return(khttpdigest_validatehash(req, skey4));
264 }
265