Add.
[shishi.git] / lib / starttls.c
blob3fefe56050b4c2a923f5f565a54efa142612bc13
1 /* starttls.c --- Network I/O functions for Shishi over TLS.
2 * Copyright (C) 2002, 2003, 2004 Simon Josefsson
4 * This file is part of Shishi.
6 * Shishi is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Shishi is distributed in the hope that it will be useful,
12 * but 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, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "internal.h"
23 #include <gnutls/gnutls.h>
24 #include "starttls.h"
26 /* Initialize TLS subsystem. Typically invoked by shishi_init. */
27 int
28 _shishi_tls_init (Shishi * handle)
30 int rc;
32 rc = gnutls_global_init ();
33 if (rc != GNUTLS_E_SUCCESS)
35 shishi_warn (handle, "TLS initialization failed: %s",
36 gnutls_strerror (rc));
37 return SHISHI_CRYPTO_INTERNAL_ERROR;
40 return SHISHI_OK;
43 /* Deinitialize TLS subsystem. Typically invoked by shishi_done. */
44 int
45 _shishi_tls_done (Shishi * handle)
47 /* XXX call gnutls_global_deinit here. But what if application uses
48 tls? what if more than one shishi handle is allocated? */
49 return SHISHI_OK;
53 * Alternative approach: First send KDC-REQ in clear with PA-STARTTLS
54 * preauth data, and have server respond with something saying it is
55 * ready to go on (what should that packet look like??), and then
56 * start tls on that session. If server doesn't support PA-STARTTLS,
57 * it will simply complain. For udp we shouldn't do anything at all.
59 * Simpler: Use leading reserved bit in TCP length field to mean
60 * STARTTLS. (Probably better to have it mean that a new octet is
61 * present, and that a 0 in that field means STARTTLS, and all other
62 * fields are reserved, for future extensions.) Yup, see complete
63 * writeup in manual.
65 * Also need to add code to map client certificate X.509 into pre
66 * authenticated principal?
68 * Derive EncKDCRepPart key from TLS PRF? Hm.
72 #define STARTTLS_CLIENT_REQUEST "\x70\x00\x00\x01"
73 #define STARTTLS_SERVER_ACCEPT "\x70\x00\x00\x02"
74 #define STARTTLS_LEN 4
76 /* Negotiate TLS and send and receive packets on an open socket. */
77 static int
78 _shishi_sendrecv_tls1 (Shishi * handle,
79 int sockfd,
80 gnutls_session session,
81 const char *indata, size_t inlen,
82 char **outdata, size_t * outlen, size_t timeout)
84 int ret;
85 ssize_t bytes_sent, bytes_read;
86 char extbuf[STARTTLS_LEN + 1];
87 static size_t session_data_size = 0;
88 static void *session_data = NULL;
90 bytes_sent = write (sockfd, STARTTLS_CLIENT_REQUEST, STARTTLS_LEN);
91 if (bytes_sent != STARTTLS_LEN)
92 return SHISHI_SENDTO_ERROR;
94 bytes_read = read (sockfd, extbuf, sizeof (extbuf));
95 if (bytes_read != STARTTLS_LEN ||
96 memcmp (extbuf, STARTTLS_SERVER_ACCEPT, STARTTLS_LEN) != 0)
97 return SHISHI_RECVFROM_ERROR;
99 gnutls_transport_set_ptr (session, (gnutls_transport_ptr) sockfd);
101 if (session_data_size > 0)
102 gnutls_session_set_data (session, session_data, session_data_size);
104 ret = gnutls_handshake (session);
105 if (ret < 0)
107 shishi_error_printf (handle, "TLS handshake failed (%d): %s",
108 ret, gnutls_strerror (ret));
109 return SHISHI_RECVFROM_ERROR;
112 if (gnutls_session_is_resumed (session) != 0)
113 shishi_error_printf (handle, "TLS handshake completed (resumed)");
114 else
115 shishi_error_printf (handle, "TLS handshake completed (not resumed)");
117 if (session_data_size == 0)
119 ret = gnutls_session_get_data (session, NULL, &session_data_size);
120 if (ret < 0)
122 shishi_error_printf (handle, "TLS gsgd(1) failed (%d): %s",
123 ret, gnutls_strerror (ret));
124 return SHISHI_RECVFROM_ERROR;
126 session_data = xmalloc (session_data_size);
127 ret = gnutls_session_get_data (session, session_data,
128 &session_data_size);
129 if (ret < 0)
131 shishi_error_printf (handle, "TLS gsgd(2) failed (%d): %s",
132 ret, gnutls_strerror (ret));
133 return SHISHI_RECVFROM_ERROR;
137 bytes_sent = gnutls_record_send (session, indata, inlen);
138 if (bytes_sent != inlen)
140 shishi_error_printf (handle, "Bad TLS write (%d < %d)",
141 bytes_sent, inlen);
142 return SHISHI_SENDTO_ERROR;
145 *outlen = gnutls_record_get_max_size (session);
146 *outdata = xmalloc (*outlen);
148 bytes_read = gnutls_record_recv (session, *outdata, *outlen);
149 if (bytes_read == 0)
151 shishi_error_printf (handle, "Peer has closed the TLS connection");
152 free (*outdata);
153 return SHISHI_RECVFROM_ERROR;
155 else if (bytes_read < 0)
157 shishi_error_printf (handle, "TLS Error (%d): %s",
158 ret, gnutls_strerror (ret));
159 free (*outdata);
160 return SHISHI_RECVFROM_ERROR;
163 *outdata = xrealloc (*outdata, bytes_read);
164 *outlen = bytes_read;
167 ret = gnutls_bye (session, GNUTLS_SHUT_RDWR);
168 while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
170 if (ret != GNUTLS_E_SUCCESS)
171 shishi_error_printf (handle, "TLS Disconnected failed (%d): %s",
172 ret, gnutls_strerror (ret));
174 return SHISHI_OK;
177 /* Send request to KDC over TLS, receive reply, and disconnect. */
179 _shishi_sendrecv_tls (Shishi * handle,
180 struct sockaddr *addr,
181 const char *indata, size_t inlen,
182 char **outdata, size_t * outlen,
183 size_t timeout, Shishi_tkts_hint * hint)
185 const int kx_prio[] = { GNUTLS_KX_RSA, GNUTLS_KX_DHE_DSS,
186 GNUTLS_KX_DHE_RSA, GNUTLS_KX_ANON_DH, 0
188 gnutls_session session;
189 gnutls_anon_client_credentials anoncred;
190 gnutls_certificate_credentials x509cred;
191 int sockfd;
192 int ret, outerr;
193 const char *certfile = shishi_x509cert_default_file (handle);
194 const char *keyfile = shishi_x509key_default_file (handle);
196 ret = gnutls_init (&session, GNUTLS_CLIENT);
197 if (ret != GNUTLS_E_SUCCESS)
199 shishi_error_printf (handle, "TLS init failed (%d): %s",
200 ret, gnutls_strerror (ret));
201 return SHISHI_CRYPTO_ERROR;
204 ret = gnutls_set_default_priority (session);
205 if (ret != GNUTLS_E_SUCCESS)
207 shishi_error_printf (handle, "TLS sdp failed (%d): %s",
208 ret, gnutls_strerror (ret));
209 return SHISHI_CRYPTO_ERROR;
212 ret = gnutls_anon_allocate_client_credentials (&anoncred);
213 if (ret != GNUTLS_E_SUCCESS)
215 shishi_error_printf (handle, "TLS aacs failed (%d): %s",
216 ret, gnutls_strerror (ret));
217 return SHISHI_CRYPTO_ERROR;
220 ret = gnutls_credentials_set (session, GNUTLS_CRD_ANON, anoncred);
221 if (ret != GNUTLS_E_SUCCESS)
223 shishi_error_printf (handle, "TLS cs failed (%d): %s",
224 ret, gnutls_strerror (ret));
225 return SHISHI_CRYPTO_ERROR;
228 ret = gnutls_certificate_allocate_credentials (&x509cred);
229 if (ret != GNUTLS_E_SUCCESS)
231 shishi_error_printf (handle, "TLS cac failed (%d): %s",
232 ret, gnutls_strerror (ret));
233 return SHISHI_CRYPTO_ERROR;
236 ret = gnutls_certificate_set_x509_key_file (x509cred, certfile,
237 keyfile, GNUTLS_X509_FMT_PEM);
238 if (ret != GNUTLS_E_SUCCESS && ret != GNUTLS_E_FILE_ERROR)
240 shishi_error_printf (handle, "TLS csxkf failed (%d): %s",
241 ret, gnutls_strerror (ret));
242 return SHISHI_CRYPTO_ERROR;
244 else if (ret == GNUTLS_E_SUCCESS)
245 shishi_error_printf (handle, "Loaded client certificate");
247 ret = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509cred);
248 if (ret != GNUTLS_E_SUCCESS)
250 shishi_error_printf (handle, "TLS cs X.509 failed (%d): %s",
251 ret, gnutls_strerror (ret));
252 return SHISHI_CRYPTO_ERROR;
255 ret = gnutls_kx_set_priority (session, kx_prio);
256 if (ret != GNUTLS_E_SUCCESS)
258 shishi_error_printf (handle, "TLS ksp failed (%d): %s",
259 ret, gnutls_strerror (ret));
260 return SHISHI_CRYPTO_ERROR;
263 sockfd = socket (AF_INET, SOCK_STREAM, 0);
264 if (sockfd < 0)
266 shishi_error_set (handle, strerror (errno));
267 return SHISHI_SOCKET_ERROR;
270 if (connect (sockfd, addr, sizeof (*addr)) != 0)
272 shishi_error_set (handle, strerror (errno));
273 close (sockfd);
274 return SHISHI_CONNECT_ERROR;
277 /* Core part. */
278 outerr = _shishi_sendrecv_tls1 (handle, sockfd, session, indata, inlen,
279 outdata, outlen, timeout);
281 ret = shutdown (sockfd, SHUT_RDWR);
282 if (ret != 0)
284 shishi_error_printf (handle, "Shutdown failed (%d): %s",
285 ret, strerror (errno));
286 if (outerr == SHISHI_OK)
287 outerr = SHISHI_CLOSE_ERROR;
290 ret = close (sockfd);
291 if (ret != 0)
293 shishi_error_printf (handle, "Close failed (%d): %s",
294 ret, strerror (errno));
295 if (outerr == SHISHI_OK)
296 outerr = SHISHI_CLOSE_ERROR;
299 gnutls_deinit (session);
300 gnutls_anon_free_client_credentials (anoncred);
302 return outerr;