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