openconnect-unknown/digest.c

1 /*
2  * OpenConnect (SSL + DTLS) VPN client
3  *
4  * Copyright © 2008-2015 Intel Corporation.
5  *
6  * Author: David Woodhouse <dwmw2@infradead.org>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1, as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  */
17 
18 #include <config.h>
19 
20 #include <errno.h>
21 #include <string.h>
22 #include <ctype.h>
23 
24 #include "openconnect-internal.h"
25 
26 #define ALGO_MD5	0
27 #define ALGO_MD5_SESS	1
28 
29 static struct oc_text_buf *get_qs(char **str)
30 {
31 	struct oc_text_buf *res;
32 	int escaped = 0;
33 	char *p = *str;
34 
35 	if (*p != '\"')
36 		return NULL;
37 
38 	res = buf_alloc();
39 
40 	while (*++p) {
41 		if (!escaped && *p == '\"') {
42 			*str = p+1;
43 			if (buf_error(res))
44 				break;
45 			return res;
46 		}
47 		if (escaped)
48 			escaped = 0;
49 		else if (*p == '\\')
50 			escaped = 1;
51 		buf_append_bytes(res, p, 1);
52 	}
53 	buf_free(res);
54 	return NULL;
55 }
56 
57 static void buf_append_unq(struct oc_text_buf *buf, const char *str)
58 {
59 	while (*str) {
60 		if (*str == '\"' || *str == '\\')
61 			buf_append(buf, "\\");
62 		buf_append_bytes(buf, str, 1);
63 		str++;
64 	}
65 }
66 
67 static void buf_append_md5(struct oc_text_buf *buf, void *data, int len)
68 {
69 	unsigned char md5[16];
70 
71 	if (openconnect_md5(md5, data, len)) {
72 		buf->error = -EIO;
73 		return;
74 	}
75 
76 	buf_append_hex(buf, md5, 16);
77 }
78 
79 int digest_authorization(struct openconnect_info *vpninfo, int proxy,
80 			 struct http_auth_state *auth_state,
81 			 struct oc_text_buf *hdrbuf)
82 {
83 	char *chall;
84 	int ret = -EINVAL;
85 	int algo = ALGO_MD5;
86 	int qop_auth = 0;
87 	int nc = 1;
88 	struct oc_text_buf *realm = NULL, *nonce = NULL, *opaque = NULL;
89 	struct oc_text_buf *a1 = NULL, *a2 = NULL, *kd = NULL;
90 	struct oc_text_buf *cnonce = NULL;
91 	unsigned char cnonce_random[32];
92 	const char *user, *pass;
93 
94 	if (proxy) {
95 		user = vpninfo->proxy_user;
96 		pass = vpninfo->proxy_pass;
97 	} else {
98 		/* Need to parse this out of the URL */
99 		return -EINVAL;
100 	}
101 
102 	if (!user || !pass)
103 		return -EINVAL;
104 
105 	if (auth_state->state < AUTH_AVAILABLE)
106 		return -EINVAL;
107 
108 	if (auth_state->state == AUTH_IN_PROGRESS) {
109 		auth_state->state = AUTH_FAILED;
110 		return -EAGAIN;
111 	}
112 
113 	chall = auth_state->challenge;
114 	if (!chall)
115 		return -EINVAL;
116 
117 	while (*chall) {
118 		if (!realm && !strncmp(chall, "realm=", 6)) {
119 			chall += 6;
120 			realm = get_qs(&chall);
121 			if (!realm)
122 				goto err;
123 		} else if (!nonce && !strncmp(chall, "nonce=", 6)) {
124 			chall += 6;
125 			nonce = get_qs(&chall);
126 			if (!nonce)
127 				goto err;
128 		} else if (!strncmp(chall, "qop=", 4)) {
129 			chall += 4;
130 			if (strncmp(chall, "\"auth\"", 6)) {
131 				/* We don't support "auth-int" */
132 				goto err;
133 			}
134 			qop_auth = 1;
135 			chall += 6;
136 		} else if (!opaque && !strncmp(chall, "opaque=", 7)) {
137 			chall += 7;
138 			opaque = get_qs(&chall);
139 			if (!opaque)
140 				goto err;
141 		} else if (!strncmp(chall, "algorithm=", 10)) {
142 			chall += 10;
143 			if (!strncmp(chall, "MD5-sess", 8)) {
144 				algo = ALGO_MD5_SESS;
145 				chall += 8;
146 			} else if (!strncmp(chall, "MD5", 3)) {
147 				algo = ALGO_MD5;
148 				chall += 3;
149 			}
150 		} else {
151 			char *p = strchr(chall, '=');
152 			if (!p)
153 				goto err;
154 			p++;
155 			if (*p == '\"') {
156 				/* Eat and discard a quoted-string */
157 				int escaped = 0;
158 				p++;
159 				do  {
160 					if (escaped)
161 						escaped = 0;
162 					else if (*p == '\\')
163 						escaped = 1;
164 					if (!*p)
165 						goto err;
166 				} while (escaped || *p != '\"');
167 				chall = p+1;
168 			} else {
169 				/* Not quoted. Just find the next comma (or EOL) */
170 				p = strchr(p, ',');
171 				if (!p)
172 					break;
173 				chall = p;
174 			}
175 		}
176 		while (isspace((int)(unsigned char)*chall))
177 			chall++;
178 		if (!*chall)
179 			break;
180 		if (*chall != ',')
181 			goto err;
182 		chall++;
183 		while (isspace((int)(unsigned char)*chall))
184 			chall++;
185 		if (!*chall)
186 			break;
187 	}
188 	if (!nonce || !realm)
189 		goto err;
190 
191 	if (openconnect_random(&cnonce_random, sizeof(cnonce_random)))
192 		goto err;
193 	cnonce = buf_alloc();
194 	buf_append_base64(cnonce, cnonce_random, sizeof(cnonce_random));
195 	if (buf_error(cnonce))
196 		goto err;
197 
198 	/*
199 	 * According to RFC2617 §3.2.2.2:
200 	 *  A1       = unq(username-value) ":" unq(realm-value) ":" passwd
201 	 * So the username is escaped, while the password isn't.
202 	 */
203 	a1 = buf_alloc();
204 	buf_append_unq(a1, user);
205 	buf_append(a1, ":%s:%s", realm->data, pass);
206 	if (buf_error(a1))
207 		goto err;
208 	if (algo == ALGO_MD5_SESS) {
209 		struct oc_text_buf *old_a1 = a1;
210 
211 		a1 = buf_alloc();
212 		buf_append_md5(a1, old_a1->data, old_a1->pos);
213 		buf_free(old_a1);
214 		buf_append(a1, ":%s:%s\n", nonce->data, cnonce->data);
215 		if (buf_error(a1))
216 			goto err;
217 	}
218 
219 	a2 = buf_alloc();
220 	buf_append(a2, "CONNECT:%s:%d", vpninfo->hostname, vpninfo->port);
221 	if (buf_error(a2))
222 		goto err;
223 
224 	kd = buf_alloc();
225 	buf_append_md5(kd, a1->data, a1->pos);
226 	buf_append(kd, ":%s:", nonce->data);
227 	if (qop_auth) {
228 		buf_append(kd, "%08x:%s:auth:", nc, cnonce->data);
229 	}
230 	buf_append_md5(kd, a2->data, a2->pos);
231 	if (buf_error(kd))
232 		goto err;
233 
234 	buf_append(hdrbuf, "%sAuthorization: Digest username=\"", proxy ? "Proxy-" : "");
235 	buf_append_unq(hdrbuf, user);
236 	buf_append(hdrbuf, "\", realm=\"%s\", nonce=\"%s\", uri=\"%s:%d\", ",
237 		   realm->data, nonce->data, vpninfo->hostname, vpninfo->port);
238 	if (qop_auth)
239 		buf_append(hdrbuf, "cnonce=\"%s\", nc=%08x, qop=auth, ",
240 			   cnonce->data, nc);
241 	if (opaque)
242 		buf_append(hdrbuf, "opaque=\"%s\", ", opaque->data);
243 	buf_append(hdrbuf, "response=\"");
244 	buf_append_md5(hdrbuf, kd->data, kd->pos);
245 	buf_append(hdrbuf, "\"\r\n");
246 
247 	ret = 0;
248 
249 	auth_state->state = AUTH_IN_PROGRESS;
250 	if (proxy)
251 		vpn_progress(vpninfo, PRG_INFO,
252 			     _("Attempting Digest authentication to proxy\n"));
253 	else
254 		vpn_progress(vpninfo, PRG_INFO,
255 			     _("Attempting Digest authentication to server '%s'\n"),
256 			     vpninfo->hostname);
257  err:
258 	if (a1 && a1->data)
259 		memset(a1->data, 0, a1->pos);
260 	buf_free(a1);
261 	buf_free(a2);
262 	buf_free(kd);
263 	buf_free(realm);
264 	buf_free(nonce);
265 	buf_free(cnonce);
266 	buf_free(opaque);
267 	return ret;
268 }