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 
23 #include "openconnect-internal.h"
24 
print_gss_err(struct openconnect_info * vpninfo,const char * where,gss_OID mech,OM_uint32 err_maj,OM_uint32 err_min)25 static void print_gss_err(struct openconnect_info *vpninfo, const char *where,
26 			  gss_OID mech, OM_uint32 err_maj, OM_uint32 err_min)
27 {
28 	OM_uint32 major, minor, msg_ctx = 0;
29 	gss_buffer_desc status;
30 
31 	do {
32 		major = gss_display_status(&minor, err_maj, GSS_C_GSS_CODE,
33 					   mech, &msg_ctx, &status);
34 		if (GSS_ERROR(major))
35 			break;
36 		vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value);
37 		gss_release_buffer(&minor, &status);
38 	} while (msg_ctx);
39 
40 	msg_ctx = 0;
41 	do {
42 		major = gss_display_status(&minor, err_min, GSS_C_MECH_CODE,
43 					   mech, &msg_ctx, &status);
44 		if (GSS_ERROR(major))
45 			break;
46 		vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value);
47 		gss_release_buffer(&minor, &status);
48 	} while (msg_ctx);
49 }
50 
51 static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
52 static const gss_OID_desc gss_mech_spnego = {
53         6,
54 	(void *)&spnego_OID
55 };
56 
gssapi_setup(struct openconnect_info * vpninfo,struct http_auth_state * auth_state,const char * service,int proxy)57 static int gssapi_setup(struct openconnect_info *vpninfo, struct http_auth_state *auth_state,
58 			const char *service, int proxy)
59 {
60 	OM_uint32 major, minor;
61 	gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
62 	char *name;
63 
64 	if (asprintf(&name, "%s@%s", service,
65 		     proxy ? vpninfo->proxy : vpninfo->hostname) == -1)
66 		return -ENOMEM;
67 	token.length = strlen(name);
68 	token.value = name;
69 
70 	major = gss_import_name(&minor, &token, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
71 				&auth_state->gss_target_name);
72 	free(name);
73 	if (GSS_ERROR(major)) {
74 		vpn_progress(vpninfo, PRG_ERR,
75 			     _("Error importing GSSAPI name for authentication:\n"));
76 		print_gss_err(vpninfo, "gss_import_name()", GSS_C_NO_OID, major, minor);
77 		return -EIO;
78 	}
79 	return 0;
80 }
81 
82 #define GSSAPI_CONTINUE	2
83 #define GSSAPI_COMPLETE	3
84 
gssapi_authorization(struct openconnect_info * vpninfo,int proxy,struct http_auth_state * auth_state,struct oc_text_buf * hdrbuf)85 int gssapi_authorization(struct openconnect_info *vpninfo, int proxy,
86 			 struct http_auth_state *auth_state,
87 			 struct oc_text_buf *hdrbuf)
88 {
89 	OM_uint32 major, minor;
90 	gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
91 	gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
92 	gss_OID mech = GSS_C_NO_OID;
93 
94 	if (auth_state->state == AUTH_AVAILABLE && gssapi_setup(vpninfo, auth_state, "HTTP", proxy)) {
95 		auth_state->state = AUTH_FAILED;
96 		return -EIO;
97 	}
98 
99 	if (auth_state->challenge && *auth_state->challenge) {
100 		int len = -EINVAL;
101 		in.value = openconnect_base64_decode(&len, auth_state->challenge);
102 		if (!in.value)
103 			return len;
104 		in.length = len;
105 	} else if (auth_state->state > AUTH_AVAILABLE) {
106 		/* This indicates failure. We were trying, but got an empty
107 		   'Proxy-Authorization: Negotiate' header back from the server
108 		   implying that we should start again... */
109 		goto fail_gssapi;
110 	}
111 
112 	major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
113 				     &auth_state->gss_context,
114 				     auth_state->gss_target_name,
115 				     (gss_OID)&gss_mech_spnego,
116 				     GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE,
117 				     GSS_C_NO_CHANNEL_BINDINGS, &in,
118 				     &mech, &out, NULL, NULL);
119 	if (in.value)
120 		free(in.value);
121 
122 	if (major == GSS_S_COMPLETE)
123 		auth_state->state = GSSAPI_COMPLETE;
124 	else if (major == GSS_S_CONTINUE_NEEDED)
125 		auth_state->state = GSSAPI_CONTINUE;
126 	else {
127 		vpn_progress(vpninfo, PRG_ERR,
128 			     _("Error generating GSSAPI response:\n"));
129 		print_gss_err(vpninfo, "gss_init_sec_context()", mech, major, minor);
130 	fail_gssapi:
131 		auth_state->state = AUTH_FAILED;
132 		cleanup_gssapi_auth(vpninfo, auth_state);
133 		/* If we were *trying*, then -EAGAIN. Else -ENOENT to let another
134 		   auth method try without having to reconnect first. */
135 		return in.value ? -EAGAIN : -ENOENT;
136 	}
137 	buf_append(hdrbuf, "%sAuthorization: Negotiate ", proxy ? "Proxy-" : "");
138 	buf_append_base64(hdrbuf, out.value, out.length);
139 	buf_append(hdrbuf, "\r\n");
140 
141 	gss_release_buffer(&minor, &out);
142 	if (!auth_state->challenge) {
143 		if (proxy)
144 			vpn_progress(vpninfo, PRG_INFO,
145 				     _("Attempting GSSAPI authentication to proxy\n"));
146 		else
147 			vpn_progress(vpninfo, PRG_INFO,
148 				     _("Attempting GSSAPI authentication to server '%s'\n"),
149 				     vpninfo->hostname);
150 	}
151 
152 	return 0;
153 }
154 
155 /* auth_state is NULL when called from socks_gssapi_auth() */
cleanup_gssapi_auth(struct openconnect_info * vpninfo,struct http_auth_state * auth_state)156 void cleanup_gssapi_auth(struct openconnect_info *vpninfo,
157 			 struct http_auth_state *auth_state)
158 {
159 	OM_uint32 minor;
160 
161 	if (auth_state->gss_target_name != GSS_C_NO_NAME)
162 		gss_release_name(&minor, &auth_state->gss_target_name);
163 
164 	if (auth_state->gss_context != GSS_C_NO_CONTEXT)
165 		gss_delete_sec_context(&minor, &auth_state->gss_context, GSS_C_NO_BUFFER);
166 
167 	/* Shouldn't be necessary, but make sure... */
168 	auth_state->gss_target_name = GSS_C_NO_NAME;
169 	auth_state->gss_context = GSS_C_NO_CONTEXT;
170 }
171 
socks_gssapi_auth(struct openconnect_info * vpninfo)172 int socks_gssapi_auth(struct openconnect_info *vpninfo)
173 {
174 	gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
175 	gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
176 	gss_OID mech = GSS_C_NO_OID;
177 	OM_uint32 major, minor;
178 	unsigned char *pktbuf;
179 	int i;
180 	int ret = -EIO;
181 	struct http_auth_state *auth_state = &vpninfo->proxy_auth[AUTH_TYPE_GSSAPI];
182 
183 	if (gssapi_setup(vpninfo, auth_state, "rcmd", 1))
184 		return -EIO;
185 
186 	pktbuf = malloc(65538);
187 	if (!pktbuf)
188 		return -ENOMEM;
189 	while (1) {
190 		major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &auth_state->gss_context,
191 					     auth_state->gss_target_name, (gss_OID)&gss_mech_spnego,
192 					     GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_DELEG_FLAG | GSS_C_SEQUENCE_FLAG,
193 					     GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &in, &mech,
194 					     &out, NULL, NULL);
195 		in.value = NULL;
196 		if (major == GSS_S_COMPLETE) {
197 			/* If we still have a token to send, send it. */
198 			if (!out.length) {
199 				vpn_progress(vpninfo, PRG_DEBUG,
200 					     _("GSSAPI authentication completed\n"));
201 				gss_release_buffer(&minor, &out);
202 				ret = 0;
203 				break;
204 			}
205 		} else if (major != GSS_S_CONTINUE_NEEDED) {
206 			print_gss_err(vpninfo, "gss_init_sec_context()", mech, major, minor);
207 			break;
208 		}
209 		if (out.length > 65535) {
210 			vpn_progress(vpninfo, PRG_ERR,
211 				     _("GSSAPI token too large (%zd bytes)\n"),
212 				     out.length);
213 			break;
214 		}
215 
216 		pktbuf[0] = 1; /* ver */
217 		pktbuf[1] = 1; /* mtyp */
218 		store_be16(pktbuf + 2, out.length);
219 		memcpy(pktbuf + 4, out.value, out.length);
220 
221 		free(out.value);
222 
223 		vpn_progress(vpninfo, PRG_TRACE,
224 			     _("Sending GSSAPI token of %zu bytes\n"), out.length + 4);
225 
226 		i = vpninfo->ssl_write(vpninfo, (void *)pktbuf, out.length + 4);
227 		if (i < 0) {
228 			vpn_progress(vpninfo, PRG_ERR,
229 				     _("Failed to send GSSAPI authentication token to proxy: %s\n"),
230 				     strerror(-i));
231 			break;
232 		}
233 
234 		i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, 4);
235 		if (i < 0) {
236 			vpn_progress(vpninfo, PRG_ERR,
237 				     _("Failed to receive GSSAPI authentication token from proxy: %s\n"),
238 				     strerror(-i));
239 			break;
240 		}
241 		if (pktbuf[1] == 0xff) {
242 			vpn_progress(vpninfo, PRG_ERR,
243 				     _("SOCKS server reported GSSAPI context failure\n"));
244 			break;
245 		} else if (pktbuf[1] != 1) {
246 			vpn_progress(vpninfo, PRG_ERR,
247 				     _("Unknown GSSAPI status response (0x%02x) from SOCKS server\n"),
248 				     pktbuf[1]);
249 			break;
250 		}
251 		in.length = load_be16(pktbuf + 2);
252 		in.value = pktbuf;
253 
254 		if (!in.length) {
255 			vpn_progress(vpninfo, PRG_DEBUG,
256 				     _("GSSAPI authentication completed\n"));
257 			ret = 0;
258 			break;
259 		}
260 
261 		i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, in.length);
262 		if (i < 0) {
263 			vpn_progress(vpninfo, PRG_ERR,
264 				     _("Failed to receive GSSAPI authentication token from proxy: %s\n"),
265 				     strerror(-i));
266 			break;
267 		}
268 		vpn_progress(vpninfo, PRG_TRACE, _("Got GSSAPI token of %zu bytes: %02x %02x %02x %02x\n"),
269 			     in.length, pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3]);
270 	}
271 
272 	if (!ret) {
273 		ret = -EIO;
274 
275 		pktbuf[0] = 0;
276 		in.value = pktbuf;
277 		in.length = 1;
278 
279 		major = gss_wrap(&minor, auth_state->gss_context, 0,
280 				 GSS_C_QOP_DEFAULT, &in, NULL, &out);
281 		if (major != GSS_S_COMPLETE) {
282 			print_gss_err(vpninfo, "gss_wrap()", mech, major, minor);
283 			goto err;
284 		}
285 
286 		pktbuf[0] = 1;
287 		pktbuf[1] = 2;
288 		store_be16(pktbuf + 2, out.length);
289 		memcpy(pktbuf + 4, out.value, out.length);
290 
291 		free(out.value);
292 
293 		vpn_progress(vpninfo, PRG_TRACE,
294 			     _("Sending GSSAPI protection negotiation of %zu bytes\n"), out.length + 4);
295 
296 		i = vpninfo->ssl_write(vpninfo, (void *)pktbuf, out.length + 4);
297 		if (i < 0) {
298 			vpn_progress(vpninfo, PRG_ERR,
299 				     _("Failed to send GSSAPI protection response to proxy: %s\n"),
300 				     strerror(-i));
301 			goto err;
302 		}
303 
304 		i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, 4);
305 		if (i < 0) {
306 			vpn_progress(vpninfo, PRG_ERR,
307 				     _("Failed to receive GSSAPI protection response from proxy: %s\n"),
308 				     strerror(-i));
309 			goto err;
310 		}
311 		in.length = load_be16(pktbuf + 2);
312 		in.value = pktbuf;
313 
314 		i = vpninfo->ssl_read(vpninfo, (void *)pktbuf, in.length);
315 		if (i < 0) {
316 			vpn_progress(vpninfo, PRG_ERR,
317 				     _("Failed to receive GSSAPI protection response from proxy: %s\n"),
318 				     strerror(-i));
319 			goto err;
320 		}
321 		vpn_progress(vpninfo, PRG_TRACE,
322 			     _("Got GSSAPI protection response of %zu bytes: %02x %02x %02x %02x\n"),
323 			     in.length, pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3]);
324 
325 		major = gss_unwrap(&minor, auth_state->gss_context, &in, &out, NULL, GSS_C_QOP_DEFAULT);
326 		if (major != GSS_S_COMPLETE) {
327 			print_gss_err(vpninfo, "gss_unwrap()", mech, major, minor);
328 			goto err;
329 		}
330 		if (out.length != 1) {
331 			vpn_progress(vpninfo, PRG_ERR,
332 				     _("Invalid GSSAPI protection response from proxy (%zu bytes)\n"),
333 				     out.length);
334 			gss_release_buffer(&minor, &out);
335 			goto err;
336 		}
337 		i = *(char *)out.value;
338 		gss_release_buffer(&minor, &out);
339 		if (i == 1) {
340 			vpn_progress(vpninfo, PRG_ERR,
341 				     _("SOCKS proxy demands message integrity, which is not supported\n"));
342 			goto err;
343 		} else if (i == 2) {
344 			vpn_progress(vpninfo, PRG_ERR,
345 				     _("SOCKS proxy demands message confidentiality, which is not supported\n"));
346 			goto err;
347 		} else if (i) {
348 			vpn_progress(vpninfo, PRG_ERR,
349 				     _("SOCKS proxy demands protection unknown type 0x%02x\n"),
350 				     (unsigned char)i);
351 			goto err;
352 		}
353 		ret = 0;
354 	}
355  err:
356 	cleanup_gssapi_auth(vpninfo, NULL);
357 	free(pktbuf);
358 
359 	return ret;
360 }
361