]> code.citadel.org Git - citadel.git/blob - citadel/client_crypto.c
* SSL/TLS support for the Citadel/UX wire protocol
[citadel.git] / citadel / client_crypto.c
1 /* $Id$ */
2
3 #include "sysdep.h"
4 #ifdef HAVE_OPENSSL
5 #include <openssl/ssl.h>
6 #include <openssl/err.h>
7 #include <openssl/rand.h>
8 #endif
9 #ifdef HAVE_PTHREAD_H
10 #include <pthread.h>
11 #endif
12 #include <unistd.h>
13 #include "citadel.h"
14 #include "client_crypto.h"
15
16 #ifdef HAVE_OPENSSL
17 SSL *ssl;
18 SSL_CTX *ssl_ctx;
19 int ssl_is_connected = 0;
20 #ifdef THREADED_CLIENT
21 pthread_mutex_t **Critters;                     /* Things that need locking */
22 #endif /* THREADED_CLIENT */
23
24 extern int serv_sock;
25 extern int server_is_local;
26 #endif /* HAVE_OPENSSL */
27
28
29 #ifdef HAVE_OPENSSL
30 /*
31  * input binary data from encrypted connection
32  */
33 void serv_read_ssl(char *buf, int bytes)
34 {
35         int len, rlen;
36         char junk[1];
37
38         len = 0;
39         while (len < bytes) {
40                 if (SSL_want_read(ssl)) {
41                         if ((SSL_write(ssl, junk, 0)) < 1) {
42                                 fprintf(stderr, "SSL_write in serv_read:\n");
43                                 ERR_print_errors_fp(stderr);
44                         }
45                 }
46                 rlen = SSL_read(ssl, &buf[len], bytes - len);
47                 if (rlen < 1) {
48                         long errval;
49
50                         errval = SSL_get_error(ssl, rlen);
51                         if (errval == SSL_ERROR_WANT_READ ||
52                                         errval == SSL_ERROR_WANT_WRITE) {
53                                 sleep(1);
54                                 continue;
55                         }
56                         if (errval == SSL_ERROR_ZERO_RETURN ||
57                                         errval == SSL_ERROR_SSL) {
58                                 endtls();
59                                 serv_read(&buf[len], bytes - len);
60                                 return;
61                         }
62                         fprintf(stderr, "SSL_read in serv_read:\n");
63                         ERR_print_errors_fp(stderr);
64                         connection_died();
65                         return;
66                 }
67                 len += rlen;
68         }
69 }
70
71
72 /*
73  * send binary to server encrypted
74  */
75 void serv_write_ssl(char *buf, int nbytes)
76 {
77         int bytes_written = 0;
78         int retval;
79         char junk[1];
80
81         while (bytes_written < nbytes) {
82                 if (SSL_want_write(ssl)) {
83                         if ((SSL_read(ssl, junk, 0)) < 1) {
84                                 fprintf(stderr, "SSL_read in serv_write:\n");
85                                 ERR_print_errors_fp(stderr);
86                         }
87                 }
88                 retval = SSL_write(ssl, &buf[bytes_written],
89                                 nbytes - bytes_written);
90                 if (retval < 1) {
91                         long errval;
92
93                         errval = SSL_get_error(ssl, retval);
94                         if (errval == SSL_ERROR_WANT_READ ||
95                                         errval == SSL_ERROR_WANT_WRITE) {
96                                 sleep(1);
97                                 continue;
98                         }
99                         if (errval == SSL_ERROR_ZERO_RETURN ||
100                                         errval == SSL_ERROR_SSL) {
101                                 endtls();
102                                 serv_write(&buf[bytes_written],
103                                                 nbytes - bytes_written);
104                                 return;
105                         }
106                         fprintf(stderr, "SSL_write in serv_write:\n");
107                         ERR_print_errors_fp(stderr);
108                         connection_died();
109                         return;
110                 }
111                 bytes_written += retval;
112         }
113 }
114
115
116 void ssl_lock(int mode, int n, const char *file, int line)
117 {
118 #ifdef THREADED_CLIENT
119         if (mode & CRYPTO_LOCK)
120                 pthread_mutex_lock(Critters[n]);
121         else
122                 pthread_mutex_unlock(Critters[n]);
123 #endif /* THREADED_CLIENT */
124 }
125 #endif /* HAVE_OPENSSL */
126
127
128 /*
129  * starttls() starts SSL/TLS if possible
130  * Returns 1 if the session is encrypted, 0 otherwise
131  */
132 int starttls(void)
133 {
134 #ifdef HAVE_OPENSSL
135         int a;
136         char buf[SIZ];
137         SSL_METHOD *ssl_method;
138         DH *dh;
139         
140         /* TLS is pointless when server is local */
141         if (server_is_local) {
142                 return 0;
143         }
144
145         /* Get started */
146         ssl = NULL;
147         ssl_ctx = NULL;
148         dh = NULL;
149         SSL_load_error_strings();
150         SSLeay_add_ssl_algorithms();
151
152         /* Set up the SSL context in which we will oeprate */
153         ssl_method = SSLv23_client_method();
154         ssl_ctx = SSL_CTX_new(ssl_method);
155         if (!ssl_ctx) {
156                 fprintf(stderr, "SSL_CTX_new failed: %s\n",
157                                 ERR_reason_error_string(ERR_get_error()));
158                 return 0;
159         }
160         /* Any reasonable cipher we can get */
161         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
162                 fprintf(stderr, "No ciphers available for encryption\n");
163                 SSL_CTX_free(ssl_ctx);
164                 ssl_ctx = NULL;
165                 return 0;
166         }
167         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
168         
169         /* Load DH parameters into the context */
170         dh = DH_new();
171         if (!dh) {
172                 fprintf(stderr, "Can't allocate a DH object: %s\n",
173                                 ERR_reason_error_string(ERR_get_error()));
174                 return 0;
175         }
176         if (!(BN_hex2bn(&(dh->p), DH_P))) {
177                 fprintf(stderr, "Can't assign DH_P: %s\n",
178                                 ERR_reason_error_string(ERR_get_error()));
179                 return 0;
180         }
181         if (!(BN_hex2bn(&(dh->g), DH_G))) {
182                 fprintf(stderr, "Can't assign DH_G: %s\n",
183                                 ERR_reason_error_string(ERR_get_error()));
184                 return 0;
185         }
186         dh->length = DH_L;
187         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
188         DH_free(dh);
189
190 #ifdef THREADED_CLIENT
191         /* OpenSSL requires callbacks for threaded clients */
192         CRYPTO_set_locking_callback(ssl_lock);
193         CRYPTO_set_id_callback(pthread_self);
194
195         /* OpenSSL requires us to do semaphores for threaded clients */
196         Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
197         if (!Critters) {
198                 perror("malloc failed");
199                 exit(1);
200         } else {
201                 for (a = 0; a < CRYPTO_num_locks(); a++) {
202                         Critters[a] = malloc(sizeof (pthread_mutex_t));
203                         if (!Critters[a]) {
204                                 perror("malloc failed");
205                                 exit(1);
206                         }
207                         pthread_mutex_init(Critters[a], NULL);
208                 }
209         }
210 #endif /* THREADED_CLIENT */
211
212         /* New SSL object */
213         ssl = SSL_new(ssl_ctx);
214         if (!ssl) {
215                 fprintf(stderr, "SSL_new failed: %s\n",
216                                 ERR_reason_error_string(ERR_get_error()));
217                 SSL_CTX_free(ssl_ctx);
218                 ssl_ctx = NULL;
219                 return 0;
220         }
221         /* Pointless flag waving */
222 #if SSLEAY_VERSION_NUMBER >= 0x0922
223         SSL_set_session_id_context(ssl, "Citadel/UX SID", 14);
224 #endif
225         if (!RAND_status()) {
226                 fprintf(stderr, "PRNG not properly seeded\n");
227                 return 0;
228         }
229
230         /* Associate network connection with SSL object */
231         if (SSL_set_fd(ssl, serv_sock) < 1) {
232                 fprintf(stderr, "SSL_set_fd failed: %s\n",
233                                 ERR_reason_error_string(ERR_get_error()));
234                 SSL_CTX_free(ssl_ctx);
235                 ssl_ctx = NULL;
236                 SSL_free(ssl);
237                 ssl = NULL;
238                 return 0;
239         }
240
241         printf("Requesting encryption...\r");
242         fflush(stdout);
243
244         /* Ready to start SSL/TLS */
245         serv_puts("STLS");
246         serv_gets(buf);
247         if (buf[0] != '2') {
248                 fprintf(stderr, "Server can't start TLS: %s\n", &buf[4]);
249                 return 0;
250         }
251
252         /* Do SSL/TLS handshake */
253         if ((a = SSL_connect(ssl)) < 1) {
254                 fprintf(stderr, "SSL_connect failed: %s\n",
255                                 ERR_reason_error_string(ERR_get_error()));
256                 SSL_CTX_free(ssl_ctx);
257                 ssl_ctx = NULL;
258                 SSL_free(ssl);
259                 ssl = NULL;
260                 return 0;
261         }
262         BIO_set_close(ssl->rbio, BIO_NOCLOSE);
263         {
264                 int bits, alg_bits;
265
266                 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ssl), &alg_bits);
267                 printf("Encrypting with %s cipher %s (%d of %d bits)\n",
268                                 SSL_CIPHER_get_version(SSL_get_current_cipher(ssl)),
269                                 SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)),
270                                 bits, alg_bits);
271         }
272         ssl_is_connected = 1;
273         return 1;
274 #else
275         return 0;
276 #endif /* HAVE_OPENSSL */
277 }
278
279
280 /*
281  * void endtls() - end SSL/TLS session
282  */
283 void endtls(void)
284 {
285 #ifdef HAVE_OPENSSL
286         if (ssl) {
287                 SSL_shutdown(ssl);
288                 SSL_free(ssl);
289                 ssl = NULL;
290         }
291         ssl_is_connected = 0;
292         if (ssl_ctx) {
293                 SSL_CTX_free(ssl_ctx);
294                 ssl_ctx = NULL;
295         }
296 #endif
297 }