Branch data Line data Source code
1 : : /* starttls.c --- Network I/O functions for Shishi over TLS.
2 : : * Copyright (C) 2002, 2003, 2004, 2006, 2007, 2008, 2010 Simon Josefsson
3 : : *
4 : : * This file is part of Shishi.
5 : : *
6 : : * Shishi is free software; you can redistribute it and/or modify it
7 : : * under the terms of the GNU General Public License as published by
8 : : * the Free Software Foundation; either version 3 of the License, or
9 : : * (at your option) any later version.
10 : : *
11 : : * Shishi is distributed in the hope that it will be useful, but
12 : : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : : * GNU General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU General Public License
17 : : * along with Shishi; if not, see http://www.gnu.org/licenses or write
18 : : * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
19 : : * Floor, Boston, MA 02110-1301, USA
20 : : *
21 : : */
22 : :
23 : : #include "internal.h"
24 : : #include <gnutls/gnutls.h>
25 : : #include "starttls.h"
26 : :
27 : : /* Initialize TLS subsystem. Typically invoked by shishi_init. */
28 : : int
29 : 14 : _shishi_tls_init (Shishi * handle)
30 : : {
31 : : int rc;
32 : :
33 : 14 : rc = gnutls_global_init ();
34 [ - + ]: 14 : if (rc != GNUTLS_E_SUCCESS)
35 : : {
36 : 0 : shishi_warn (handle, "TLS initialization failed: %s",
37 : : gnutls_strerror (rc));
38 : 0 : return SHISHI_CRYPTO_INTERNAL_ERROR;
39 : : }
40 : :
41 : 14 : return SHISHI_OK;
42 : : }
43 : :
44 : : /* Deinitialize TLS subsystem. Typically invoked by shishi_done. */
45 : : int
46 : 14 : _shishi_tls_done (Shishi * handle)
47 : : {
48 : : /* XXX call gnutls_global_deinit here. But what if application uses
49 : : tls? what if more than one shishi handle is allocated? */
50 : 14 : return SHISHI_OK;
51 : : }
52 : :
53 : : /*
54 : : * Alternative approach: First send KDC-REQ in clear with PA-STARTTLS
55 : : * preauth data, and have server respond with something saying it is
56 : : * ready to go on (what should that packet look like??), and then
57 : : * start tls on that session. If server doesn't support PA-STARTTLS,
58 : : * it will simply complain. For udp we shouldn't do anything at all.
59 : : *
60 : : * Simpler: Use leading reserved bit in TCP length field to mean
61 : : * STARTTLS. (Probably better to have it mean that a new octet is
62 : : * present, and that a 0 in that field means STARTTLS, and all other
63 : : * fields are reserved, for future extensions.) Yup, see complete
64 : : * writeup in manual.
65 : : *
66 : : * Also need to add code to map client certificate X.509 into pre
67 : : * authenticated principal?
68 : : *
69 : : * Derive EncKDCRepPart key from TLS PRF? Hm.
70 : : *
71 : : * The code currently implements rfc5021.txt and
72 : : * draft-josefsson-kerberos5-starttls-02.txt.
73 : : */
74 : :
75 : : #define STARTTLS_CLIENT_REQUEST "\x80\x00\x00\x01"
76 : : #define STARTTLS_SERVER_ACCEPT "\x00\x00\x00\x00"
77 : : #define STARTTLS_LEN 4
78 : :
79 : : #define C2I(buf) ((buf[3] & 0xFF) | \
80 : : ((buf[2] & 0xFF) << 8) | \
81 : : ((buf[1] & 0xFF) << 16) | \
82 : : ((buf[0] & 0xFF) << 24))
83 : :
84 : : /* Negotiate TLS and send and receive packets on an open socket. */
85 : : static int
86 : 0 : _shishi_sendrecv_tls1 (Shishi * handle,
87 : : int sockfd,
88 : : gnutls_session session,
89 : : const char *indata, size_t inlen,
90 : : char **outdata, size_t * outlen,
91 : : size_t timeout, bool have_cas)
92 : : {
93 : : int ret;
94 : : ssize_t bytes_sent, bytes_read;
95 : : char extbuf[STARTTLS_LEN + 1];
96 : : static size_t session_data_size = 0;
97 : : static void *session_data = NULL;
98 : : char tmpbuf[4];
99 : : unsigned int status;
100 : :
101 : 0 : bytes_sent = write (sockfd, STARTTLS_CLIENT_REQUEST, STARTTLS_LEN);
102 [ # # ]: 0 : if (bytes_sent != STARTTLS_LEN)
103 : 0 : return SHISHI_SENDTO_ERROR;
104 : :
105 : 0 : bytes_read = read (sockfd, extbuf, sizeof (extbuf));
106 [ # # # # ]: 0 : if (bytes_read != STARTTLS_LEN ||
107 : 0 : memcmp (extbuf, STARTTLS_SERVER_ACCEPT, STARTTLS_LEN) != 0)
108 : 0 : return SHISHI_RECVFROM_ERROR;
109 : :
110 : 0 : gnutls_transport_set_ptr (session, (gnutls_transport_ptr) sockfd);
111 : :
112 [ # # ]: 0 : if (session_data_size > 0)
113 : 0 : gnutls_session_set_data (session, session_data, session_data_size);
114 : :
115 : 0 : ret = gnutls_handshake (session);
116 [ # # ]: 0 : if (ret < 0)
117 : : {
118 : 0 : shishi_error_printf (handle, "TLS handshake failed (%d): %s",
119 : : ret, gnutls_strerror (ret));
120 : 0 : return SHISHI_RECVFROM_ERROR;
121 : : }
122 : :
123 [ # # ]: 0 : if (gnutls_session_is_resumed (session) != 0)
124 : 0 : shishi_error_printf (handle, "TLS handshake completed (resumed)");
125 : : else
126 : 0 : shishi_error_printf (handle, "TLS handshake completed (not resumed)");
127 : :
128 [ # # ]: 0 : if (have_cas)
129 : : {
130 : 0 : ret = gnutls_certificate_verify_peers2 (session, &status);
131 [ # # # # ]: 0 : if (ret != 0 || status != 0)
132 : : {
133 : 0 : shishi_error_printf (handle,
134 : : "TLS verification of CA failed (%d/%d)", ret,
135 : : status);
136 : 0 : return SHISHI_RECVFROM_ERROR;
137 : : }
138 : :
139 : : /* XXX: We need to verify the CA cert further here. */
140 : : }
141 : :
142 [ # # ]: 0 : if (session_data_size == 0)
143 : : {
144 : 0 : ret = gnutls_session_get_data (session, NULL, &session_data_size);
145 [ # # ]: 0 : if (ret < 0)
146 : : {
147 : 0 : shishi_error_printf (handle, "TLS gsgd(1) failed (%d): %s",
148 : : ret, gnutls_strerror (ret));
149 : 0 : return SHISHI_RECVFROM_ERROR;
150 : : }
151 : 0 : session_data = xmalloc (session_data_size);
152 : 0 : ret = gnutls_session_get_data (session, session_data,
153 : : &session_data_size);
154 [ # # ]: 0 : if (ret < 0)
155 : : {
156 : 0 : shishi_error_printf (handle, "TLS gsgd(2) failed (%d): %s",
157 : : ret, gnutls_strerror (ret));
158 : 0 : return SHISHI_RECVFROM_ERROR;
159 : : }
160 : : }
161 : :
162 : 0 : tmpbuf[3] = inlen & 0xFF;
163 : 0 : tmpbuf[2] = (inlen >> 8) & 0xFF;
164 : 0 : tmpbuf[1] = (inlen >> 16) & 0xFF;
165 : 0 : tmpbuf[0] = (inlen >> 24) & 0xFF;
166 : :
167 : 0 : bytes_sent = gnutls_record_send (session, tmpbuf, 4);
168 [ # # ]: 0 : if (bytes_sent != 4)
169 : : {
170 : 0 : shishi_error_printf (handle, "Bad TLS write (%d < 4)", bytes_sent);
171 : 0 : return SHISHI_SENDTO_ERROR;
172 : : }
173 : :
174 : 0 : bytes_sent = gnutls_record_send (session, indata, inlen);
175 [ # # ]: 0 : if (bytes_sent != (ssize_t) inlen)
176 : : {
177 : 0 : shishi_error_printf (handle, "Bad TLS write (%d < %d)",
178 : : bytes_sent, inlen);
179 : 0 : return SHISHI_SENDTO_ERROR;
180 : : }
181 : :
182 : 0 : bytes_read = gnutls_record_recv (session, tmpbuf, 4);
183 [ # # ]: 0 : if (bytes_read != 4)
184 : : {
185 : 0 : shishi_error_printf (handle, "Bad TLS read (%d < 4)", bytes_read);
186 : 0 : return SHISHI_SENDTO_ERROR;
187 : : }
188 : :
189 : : /* XXX sanities input. */
190 : 0 : *outlen = C2I (tmpbuf);
191 : 0 : *outdata = xmalloc (*outlen);
192 : :
193 : 0 : bytes_read = gnutls_record_recv (session, *outdata, *outlen);
194 [ # # ]: 0 : if (bytes_read == 0)
195 : : {
196 : 0 : shishi_error_printf (handle, "Peer has closed the TLS connection");
197 : 0 : free (*outdata);
198 : 0 : return SHISHI_RECVFROM_ERROR;
199 : : }
200 [ # # ]: 0 : else if (bytes_read < 0)
201 : : {
202 : 0 : shishi_error_printf (handle, "TLS Error (%d): %s",
203 : : ret, gnutls_strerror (ret));
204 : 0 : free (*outdata);
205 : 0 : return SHISHI_RECVFROM_ERROR;
206 : : }
207 [ # # ]: 0 : else if (bytes_read != (ssize_t) * outlen)
208 : : {
209 : 0 : shishi_error_printf (handle, "TLS Read error (%d != %d)",
210 : : *outlen, bytes_read);
211 : 0 : free (*outdata);
212 : 0 : return SHISHI_RECVFROM_ERROR;
213 : : }
214 : :
215 : : do
216 : 0 : ret = gnutls_bye (session, GNUTLS_SHUT_RDWR);
217 [ # # # # ]: 0 : while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
218 : :
219 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
220 : 0 : shishi_error_printf (handle, "TLS Disconnected failed (%d): %s",
221 : : ret, gnutls_strerror (ret));
222 : :
223 : 0 : return SHISHI_OK;
224 : : }
225 : :
226 : : /* Send request to KDC over TLS, receive reply, and disconnect. */
227 : : int
228 : 0 : _shishi_sendrecv_tls (Shishi * handle,
229 : : struct addrinfo *ai,
230 : : const char *indata, size_t inlen,
231 : : char **outdata, size_t * outlen)
232 : : {
233 : : const int kx_prio[] = { GNUTLS_KX_RSA, GNUTLS_KX_DHE_DSS,
234 : : GNUTLS_KX_DHE_RSA, GNUTLS_KX_ANON_DH, 0
235 : 0 : };
236 : : gnutls_session session;
237 : : gnutls_anon_client_credentials anoncred;
238 : : gnutls_certificate_credentials x509cred;
239 : : int sockfd;
240 : : int ret, outerr;
241 : 0 : const char *cafile = shishi_x509ca_default_file (handle);
242 : 0 : const char *certfile = shishi_x509cert_default_file (handle);
243 : 0 : const char *keyfile = shishi_x509key_default_file (handle);
244 : 0 : bool have_cas = false;
245 : :
246 : 0 : sockfd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
247 [ # # ]: 0 : if (sockfd < 0)
248 : : {
249 : 0 : shishi_error_set (handle, strerror (errno));
250 : 0 : return SHISHI_SOCKET_ERROR;
251 : : }
252 : :
253 [ # # ]: 0 : if (connect (sockfd, ai->ai_addr, ai->ai_addrlen) != 0)
254 : : {
255 : 0 : shishi_error_set (handle, strerror (errno));
256 : 0 : close (sockfd);
257 : 0 : return SHISHI_BIND_ERROR;
258 : : }
259 : :
260 : 0 : ret = gnutls_init (&session, GNUTLS_CLIENT);
261 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
262 : : {
263 : 0 : shishi_error_printf (handle, "TLS init failed (%d): %s",
264 : : ret, gnutls_strerror (ret));
265 : 0 : return SHISHI_CRYPTO_ERROR;
266 : : }
267 : :
268 : 0 : ret = gnutls_set_default_priority (session);
269 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
270 : : {
271 : 0 : shishi_error_printf (handle, "TLS sdp failed (%d): %s",
272 : : ret, gnutls_strerror (ret));
273 : 0 : return SHISHI_CRYPTO_ERROR;
274 : : }
275 : :
276 : 0 : ret = gnutls_anon_allocate_client_credentials (&anoncred);
277 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
278 : : {
279 : 0 : shishi_error_printf (handle, "TLS aacs failed (%d): %s",
280 : : ret, gnutls_strerror (ret));
281 : 0 : return SHISHI_CRYPTO_ERROR;
282 : : }
283 : :
284 : 0 : ret = gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred);
285 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
286 : : {
287 : 0 : shishi_error_printf (handle, "TLS cs failed (%d): %s",
288 : : ret, gnutls_strerror (ret));
289 : 0 : return SHISHI_CRYPTO_ERROR;
290 : : }
291 : :
292 : 0 : ret = gnutls_certificate_allocate_credentials (&x509cred);
293 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
294 : : {
295 : 0 : shishi_error_printf (handle, "TLS cac failed (%d): %s",
296 : : ret, gnutls_strerror (ret));
297 : 0 : return SHISHI_CRYPTO_ERROR;
298 : : }
299 : :
300 : 0 : ret = gnutls_certificate_set_x509_trust_file (x509cred, cafile,
301 : : GNUTLS_X509_FMT_PEM);
302 [ # # # # ]: 0 : if (ret != GNUTLS_E_SUCCESS && ret != GNUTLS_E_FILE_ERROR)
303 : : {
304 : 0 : shishi_error_printf (handle, "TLS csxtf failed (%d): %s",
305 : : ret, gnutls_strerror (ret));
306 : 0 : return SHISHI_CRYPTO_ERROR;
307 : : }
308 [ # # ]: 0 : else if (ret == GNUTLS_E_SUCCESS)
309 : : {
310 : 0 : shishi_error_printf (handle, "Loaded CA certificate");
311 : 0 : have_cas = true;
312 : : }
313 : :
314 : 0 : ret = gnutls_certificate_set_x509_key_file (x509cred, certfile,
315 : : keyfile, GNUTLS_X509_FMT_PEM);
316 [ # # # # ]: 0 : if (ret != GNUTLS_E_SUCCESS && ret != GNUTLS_E_FILE_ERROR)
317 : : {
318 : 0 : shishi_error_printf (handle, "TLS csxkf failed (%d): %s",
319 : : ret, gnutls_strerror (ret));
320 : 0 : return SHISHI_CRYPTO_ERROR;
321 : : }
322 [ # # ]: 0 : else if (ret == GNUTLS_E_SUCCESS)
323 : 0 : shishi_error_printf (handle, "Loaded client certificate");
324 : :
325 : 0 : ret = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509cred);
326 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
327 : : {
328 : 0 : shishi_error_printf (handle, "TLS cs X.509 failed (%d): %s",
329 : : ret, gnutls_strerror (ret));
330 : 0 : return SHISHI_CRYPTO_ERROR;
331 : : }
332 : :
333 : 0 : ret = gnutls_kx_set_priority (session, kx_prio);
334 [ # # ]: 0 : if (ret != GNUTLS_E_SUCCESS)
335 : : {
336 : 0 : shishi_error_printf (handle, "TLS ksp failed (%d): %s",
337 : : ret, gnutls_strerror (ret));
338 : 0 : return SHISHI_CRYPTO_ERROR;
339 : : }
340 : :
341 : : /* Core part. */
342 : 0 : outerr = _shishi_sendrecv_tls1 (handle, sockfd, session, indata, inlen,
343 : : outdata, outlen, handle->kdctimeout,
344 : : have_cas);
345 : :
346 : 0 : ret = shutdown (sockfd, SHUT_RDWR);
347 [ # # ]: 0 : if (ret != 0)
348 : : {
349 : 0 : shishi_error_printf (handle, "Shutdown failed (%d): %s",
350 : : ret, strerror (errno));
351 [ # # ]: 0 : if (outerr == SHISHI_OK)
352 : 0 : outerr = SHISHI_CLOSE_ERROR;
353 : : }
354 : :
355 : 0 : ret = close (sockfd);
356 [ # # ]: 0 : if (ret != 0)
357 : : {
358 : 0 : shishi_error_printf (handle, "Close failed (%d): %s",
359 : : ret, strerror (errno));
360 [ # # ]: 0 : if (outerr == SHISHI_OK)
361 : 0 : outerr = SHISHI_CLOSE_ERROR;
362 : : }
363 : :
364 : 0 : gnutls_deinit (session);
365 : 0 : gnutls_anon_free_client_credentials (anoncred);
366 : :
367 : 0 : return outerr;
368 : : }
|