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
24 #include <gnutls/gnutls.h>
27 /* Initialize TLS subsystem. Typically invoked by shishi_init. */
29 _shishi_tls_init (Shishi
* handle
)
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
;
44 /* Deinitialize TLS subsystem. Typically invoked by shishi_done. */
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? */
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
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. */
87 _shishi_sendrecv_tls1 (Shishi
* handle
,
89 gnutls_session session
,
90 const char *indata
, size_t inlen
,
91 char **outdata
, size_t * outlen
, size_t timeout
)
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
;
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
);
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)");
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)",
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
);
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
,
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);
166 shishi_error_printf (handle
, "Bad TLS write (%d < 4)",
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)",
176 return SHISHI_SENDTO_ERROR
;
179 bytes_read
= gnutls_record_recv (session
, tmpbuf
, 4);
182 shishi_error_printf (handle
, "Bad TLS read (%d < 4)",
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
);
194 shishi_error_printf (handle
, "Peer has closed the TLS connection");
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
));
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
);
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
));
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
;
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);
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
));
333 return SHISHI_CONNECT_ERROR
;
337 outerr
= _shishi_sendrecv_tls1 (handle
, sockfd
, session
, indata
, inlen
,
338 outdata
, outlen
, timeout
);
340 ret
= shutdown (sockfd
, SHUT_RDWR
);
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
);
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
);