Add.
[shishi.git] / lib / starttls.c
blobacb3129fcc381c600a5b597e2baf6a0d9e9b17c8
1 /* starttls.c --- Network I/O functions for Shishi over TLS.
2 * Copyright (C) 2002, 2003, 2004, 2006, 2007 Simon Josefsson
4 * This file is part of Shishi.
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.
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.
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
23 #include "internal.h"
24 #include <gnutls/gnutls.h>
25 #include "starttls.h"
27 /* Initialize TLS subsystem. Typically invoked by shishi_init. */
28 int
29 _shishi_tls_init (Shishi * handle)
31 int rc;
33 rc = gnutls_global_init ();
34 if (rc != GNUTLS_E_SUCCESS)
36 shishi_warn (handle, "TLS initialization failed: %s",
37 gnutls_strerror (rc));
38 return SHISHI_CRYPTO_INTERNAL_ERROR;
41 return SHISHI_OK;
44 /* Deinitialize TLS subsystem. Typically invoked by shishi_done. */
45 int
46 _shishi_tls_done (Shishi * handle)
48 /* XXX call gnutls_global_deinit here. But what if application uses
49 tls? what if more than one shishi handle is allocated? */
50 return SHISHI_OK;
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.
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.
66 * Also need to add code to map client certificate X.509 into pre
67 * authenticated principal?
69 * Derive EncKDCRepPart key from TLS PRF? Hm.
71 * The code currently implements
72 * draft-josefsson-krb-tcp-expansion-02.txt and
73 * draft-josefsson-kerberos5-starttls-02.txt.
76 #define STARTTLS_CLIENT_REQUEST "\x70\x00\x00\x01"
77 #define STARTTLS_SERVER_ACCEPT "\x00\x00\x00\x00"
78 #define STARTTLS_LEN 4
80 #define C2I(buf) ((buf[3] & 0xFF) | \
81 ((buf[2] & 0xFF) << 8) | \
82 ((buf[1] & 0xFF) << 16) | \
83 ((buf[0] & 0xFF) << 24))
85 /* Negotiate TLS and send and receive packets on an open socket. */
86 static int
87 _shishi_sendrecv_tls1 (Shishi * handle,
88 int sockfd,
89 gnutls_session session,
90 const char *indata, size_t inlen,
91 char **outdata, size_t * outlen, size_t timeout)
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;
101 bytes_sent = write (sockfd, STARTTLS_CLIENT_REQUEST, STARTTLS_LEN);
102 if (bytes_sent != STARTTLS_LEN)
103 return SHISHI_SENDTO_ERROR;
105 bytes_read = read (sockfd, extbuf, sizeof (extbuf));
106 if (bytes_read != STARTTLS_LEN ||
107 memcmp (extbuf, STARTTLS_SERVER_ACCEPT, STARTTLS_LEN) != 0)
108 return SHISHI_RECVFROM_ERROR;
110 gnutls_transport_set_ptr (session, (gnutls_transport_ptr) sockfd);
112 if (session_data_size > 0)
113 gnutls_session_set_data (session, session_data, session_data_size);
115 ret = gnutls_handshake (session);
116 if (ret < 0)
118 shishi_error_printf (handle, "TLS handshake failed (%d): %s",
119 ret, gnutls_strerror (ret));
120 return SHISHI_RECVFROM_ERROR;
123 if (gnutls_session_is_resumed (session) != 0)
124 shishi_error_printf (handle, "TLS handshake completed (resumed)");
125 else
126 shishi_error_printf (handle, "TLS handshake completed (not resumed)");
128 ret = gnutls_certificate_verify_peers2 (session, &status);
129 if (ret != 0 || status != 0)
131 shishi_error_printf (handle, "TLS verification of CA failed (%d/%d)",
132 ret, status);
133 return SHISHI_RECVFROM_ERROR;
136 /* XXX: We need to verify the CA cert further here. */
138 if (session_data_size == 0)
140 ret = gnutls_session_get_data (session, NULL, &session_data_size);
141 if (ret < 0)
143 shishi_error_printf (handle, "TLS gsgd(1) failed (%d): %s",
144 ret, gnutls_strerror (ret));
145 return SHISHI_RECVFROM_ERROR;
147 session_data = xmalloc (session_data_size);
148 ret = gnutls_session_get_data (session, session_data,
149 &session_data_size);
150 if (ret < 0)
152 shishi_error_printf (handle, "TLS gsgd(2) failed (%d): %s",
153 ret, gnutls_strerror (ret));
154 return SHISHI_RECVFROM_ERROR;
158 tmpbuf[3] = inlen & 0xFF;
159 tmpbuf[2] = (inlen >> 8) & 0xFF;
160 tmpbuf[1] = (inlen >> 16) & 0xFF;
161 tmpbuf[0] = (inlen >> 24) & 0xFF;
163 bytes_sent = gnutls_record_send (session, tmpbuf, 4);
164 if (bytes_sent != 4)
166 shishi_error_printf (handle, "Bad TLS write (%d < 4)",
167 bytes_sent);
168 return SHISHI_SENDTO_ERROR;
171 bytes_sent = gnutls_record_send (session, indata, inlen);
172 if (bytes_sent != inlen)
174 shishi_error_printf (handle, "Bad TLS write (%d < %d)",
175 bytes_sent, inlen);
176 return SHISHI_SENDTO_ERROR;
179 bytes_read = gnutls_record_recv (session, tmpbuf, 4);
180 if (bytes_read != 4)
182 shishi_error_printf (handle, "Bad TLS read (%d < 4)",
183 bytes_read);
184 return SHISHI_SENDTO_ERROR;
187 /* XXX sanities input. */
188 *outlen = C2I(tmpbuf);
189 *outdata = xmalloc (*outlen);
191 bytes_read = gnutls_record_recv (session, *outdata, *outlen);
192 if (bytes_read == 0)
194 shishi_error_printf (handle, "Peer has closed the TLS connection");
195 free (*outdata);
196 return SHISHI_RECVFROM_ERROR;
198 else if (bytes_read < 0)
200 shishi_error_printf (handle, "TLS Error (%d): %s",
201 ret, gnutls_strerror (ret));
202 free (*outdata);
203 return SHISHI_RECVFROM_ERROR;
205 else if (bytes_read != *outlen)
207 shishi_error_printf (handle, "TLS Read error (%d != %d)",
208 *outlen, bytes_read);
209 free (*outdata);
210 return SHISHI_RECVFROM_ERROR;
214 ret = gnutls_bye (session, GNUTLS_SHUT_RDWR);
215 while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
217 if (ret != GNUTLS_E_SUCCESS)
218 shishi_error_printf (handle, "TLS Disconnected failed (%d): %s",
219 ret, gnutls_strerror (ret));
221 return SHISHI_OK;
224 /* Send request to KDC over TLS, receive reply, and disconnect. */
226 _shishi_sendrecv_tls (Shishi * handle,
227 struct sockaddr *addr,
228 const char *indata, size_t inlen,
229 char **outdata, size_t * outlen,
230 size_t timeout, Shishi_tkts_hint * hint)
232 const int kx_prio[] = { GNUTLS_KX_RSA, GNUTLS_KX_DHE_DSS,
233 GNUTLS_KX_DHE_RSA, GNUTLS_KX_ANON_DH, 0
235 gnutls_session session;
236 gnutls_anon_client_credentials anoncred;
237 gnutls_certificate_credentials x509cred;
238 int sockfd;
239 int ret, outerr;
240 const char *cafile = shishi_x509ca_default_file (handle);
241 const char *certfile = shishi_x509cert_default_file (handle);
242 const char *keyfile = shishi_x509key_default_file (handle);
244 ret = gnutls_init (&session, GNUTLS_CLIENT);
245 if (ret != GNUTLS_E_SUCCESS)
247 shishi_error_printf (handle, "TLS init failed (%d): %s",
248 ret, gnutls_strerror (ret));
249 return SHISHI_CRYPTO_ERROR;
252 ret = gnutls_set_default_priority (session);
253 if (ret != GNUTLS_E_SUCCESS)
255 shishi_error_printf (handle, "TLS sdp failed (%d): %s",
256 ret, gnutls_strerror (ret));
257 return SHISHI_CRYPTO_ERROR;
260 ret = gnutls_anon_allocate_client_credentials (&anoncred);
261 if (ret != GNUTLS_E_SUCCESS)
263 shishi_error_printf (handle, "TLS aacs failed (%d): %s",
264 ret, gnutls_strerror (ret));
265 return SHISHI_CRYPTO_ERROR;
268 ret = gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred);
269 if (ret != GNUTLS_E_SUCCESS)
271 shishi_error_printf (handle, "TLS cs failed (%d): %s",
272 ret, gnutls_strerror (ret));
273 return SHISHI_CRYPTO_ERROR;
276 ret = gnutls_certificate_allocate_credentials (&x509cred);
277 if (ret != GNUTLS_E_SUCCESS)
279 shishi_error_printf (handle, "TLS cac failed (%d): %s",
280 ret, gnutls_strerror (ret));
281 return SHISHI_CRYPTO_ERROR;
284 ret = gnutls_certificate_set_x509_trust_file (x509cred, cafile,
285 GNUTLS_X509_FMT_PEM);
286 if (ret != GNUTLS_E_SUCCESS && ret != GNUTLS_E_FILE_ERROR)
288 shishi_error_printf (handle, "TLS csxtf failed (%d): %s",
289 ret, gnutls_strerror (ret));
290 return SHISHI_CRYPTO_ERROR;
292 else if (ret == GNUTLS_E_SUCCESS)
293 shishi_error_printf (handle, "Loaded CA certificate");
295 ret = gnutls_certificate_set_x509_key_file (x509cred, certfile,
296 keyfile, GNUTLS_X509_FMT_PEM);
297 if (ret != GNUTLS_E_SUCCESS && ret != GNUTLS_E_FILE_ERROR)
299 shishi_error_printf (handle, "TLS csxkf failed (%d): %s",
300 ret, gnutls_strerror (ret));
301 return SHISHI_CRYPTO_ERROR;
303 else if (ret == GNUTLS_E_SUCCESS)
304 shishi_error_printf (handle, "Loaded client certificate");
306 ret = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509cred);
307 if (ret != GNUTLS_E_SUCCESS)
309 shishi_error_printf (handle, "TLS cs X.509 failed (%d): %s",
310 ret, gnutls_strerror (ret));
311 return SHISHI_CRYPTO_ERROR;
314 ret = gnutls_kx_set_priority (session, kx_prio);
315 if (ret != GNUTLS_E_SUCCESS)
317 shishi_error_printf (handle, "TLS ksp failed (%d): %s",
318 ret, gnutls_strerror (ret));
319 return SHISHI_CRYPTO_ERROR;
322 sockfd = socket (AF_INET, SOCK_STREAM, 0);
323 if (sockfd < 0)
325 shishi_error_set (handle, strerror (errno));
326 return SHISHI_SOCKET_ERROR;
329 if (connect (sockfd, addr, sizeof (*addr)) != 0)
331 shishi_error_set (handle, strerror (errno));
332 close (sockfd);
333 return SHISHI_CONNECT_ERROR;
336 /* Core part. */
337 outerr = _shishi_sendrecv_tls1 (handle, sockfd, session, indata, inlen,
338 outdata, outlen, timeout);
340 ret = shutdown (sockfd, SHUT_RDWR);
341 if (ret != 0)
343 shishi_error_printf (handle, "Shutdown failed (%d): %s",
344 ret, strerror (errno));
345 if (outerr == SHISHI_OK)
346 outerr = SHISHI_CLOSE_ERROR;
349 ret = close (sockfd);
350 if (ret != 0)
352 shishi_error_printf (handle, "Close failed (%d): %s",
353 ret, strerror (errno));
354 if (outerr == SHISHI_OK)
355 outerr = SHISHI_CLOSE_ERROR;
358 gnutls_deinit (session);
359 gnutls_anon_free_client_credentials (anoncred);
361 return outerr;