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
23 #include <gnutls/gnutls.h>
26 /* Initialize TLS subsystem. Typically invoked by shishi_init. */
28 _shishi_tls_init (Shishi
* handle
)
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
;
43 /* Deinitialize TLS subsystem. Typically invoked by shishi_done. */
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? */
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
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. */
78 _shishi_sendrecv_tls1 (Shishi
* handle
,
80 gnutls_session session
,
81 const char *indata
, size_t inlen
,
82 char **outdata
, size_t * outlen
, size_t timeout
)
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
);
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)");
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
);
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
,
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)",
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
);
151 shishi_error_printf (handle
, "Peer has closed the TLS connection");
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
));
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
));
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
;
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);
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
));
274 return SHISHI_CONNECT_ERROR
;
278 outerr
= _shishi_sendrecv_tls1 (handle
, sockfd
, session
, indata
, inlen
,
279 outdata
, outlen
, timeout
);
281 ret
= shutdown (sockfd
, SHUT_RDWR
);
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
);
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
);