diff options
Diffstat (limited to 'vchat-ssl.c')
-rwxr-xr-x | vchat-ssl.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/vchat-ssl.c b/vchat-ssl.c new file mode 100755 index 0000000..5d68309 --- /dev/null +++ b/vchat-ssl.c | |||
@@ -0,0 +1,467 @@ | |||
1 | /* | ||
2 | * vchat-client - alpha version | ||
3 | * vchat-ssl.c - handling of SSL connection and X509 certificate | ||
4 | * verification | ||
5 | * | ||
6 | * Copyright (C) 2007 Thorsten Schroeder <ths@berlin.ccc.de> | ||
7 | * | ||
8 | * This program is free software. It can be redistributed and/or modified, | ||
9 | * provided that this copyright notice is kept intact. This program is | ||
10 | * distributed in the hope that it will be useful, but without any warranty; | ||
11 | * without even the implied warranty of merchantability or fitness for a | ||
12 | * particular purpose. In no event shall the copyright holder be liable for | ||
13 | * any direct, indirect, incidental or special damages arising in any way out | ||
14 | * of the use of this software. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <stdio.h> | ||
19 | #include <stdlib.h> | ||
20 | #include <string.h> | ||
21 | |||
22 | #include <openssl/err.h> | ||
23 | #include <openssl/ssl.h> | ||
24 | #include <openssl/bio.h> | ||
25 | #include <openssl/evp.h> | ||
26 | #include <openssl/x509.h> | ||
27 | #include <openssl/x509v3.h> | ||
28 | #include <openssl/conf.h> | ||
29 | |||
30 | #include "vchat.h" | ||
31 | #include "vchat-ssl.h" | ||
32 | |||
33 | char *vchat_ssl_version = "$Id$"; | ||
34 | |||
35 | static int ignore_ssl; | ||
36 | |||
37 | #define VC_CTX_ERR_EXIT(se, cx) do { \ | ||
38 | snprintf(tmpstr, TMPSTRSIZE, "CREATE CTX: %s", \ | ||
39 | ERR_error_string (ERR_get_error (), NULL)); \ | ||
40 | writecf(FS_ERR, tmpstr); \ | ||
41 | if(se) X509_STORE_free(se); \ | ||
42 | if(cx) SSL_CTX_free(cx); \ | ||
43 | return(0); \ | ||
44 | } while(0) | ||
45 | |||
46 | #define VC_SETCERT_ERR_EXIT(se, cx, err) do { \ | ||
47 | snprintf(tmpstr, TMPSTRSIZE, "CREATE CTX: %s", err); \ | ||
48 | writecf(FS_ERR, tmpstr); \ | ||
49 | if(se) X509_STORE_free(se); \ | ||
50 | if(cx) SSL_CTX_free(cx); \ | ||
51 | return(NULL); \ | ||
52 | } while(0) | ||
53 | |||
54 | SSL_CTX * vc_create_sslctx( vc_x509store_t *vc_store ) | ||
55 | { | ||
56 | int i = 0; | ||
57 | int n = 0; | ||
58 | int flags = 0; | ||
59 | int r = 0; | ||
60 | SSL_CTX *ctx = NULL; | ||
61 | X509_STORE *store = NULL; | ||
62 | vc_x509verify_cb_t verify_callback = NULL; | ||
63 | |||
64 | if( !(ctx = SSL_CTX_new(SSLv3_method())) ) | ||
65 | VC_CTX_ERR_EXIT(store, ctx); | ||
66 | |||
67 | if( !(store = vc_x509store_create(vc_store)) ) | ||
68 | VC_CTX_ERR_EXIT(store, ctx); | ||
69 | |||
70 | SSL_CTX_set_cert_store(ctx, store); | ||
71 | store = NULL; | ||
72 | SSL_CTX_set_options(ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); | ||
73 | SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); | ||
74 | |||
75 | SSL_CTX_set_verify_depth (ctx, 2); | ||
76 | |||
77 | if( !(verify_callback = vc_store->callback) ) | ||
78 | verify_callback = vc_verify_callback; | ||
79 | |||
80 | if( !(vc_store->flags & VC_X509S_SSL_VERIFY_MASK) ) { | ||
81 | writecf(FS_DBG, tmpstr); | ||
82 | flags = SSL_VERIFY_NONE; | ||
83 | } | ||
84 | else { | ||
85 | if(vc_store->flags & VC_X509S_SSL_VERIFY_PEER) | ||
86 | flags |= SSL_VERIFY_PEER; | ||
87 | if(vc_store->flags & VC_X509S_SSL_VERIFY_FAIL_IF_NO_PEER_CERT) | ||
88 | flags |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; | ||
89 | if(vc_store->flags & VC_X509S_SSL_VERIFY_CLIENT_ONCE) | ||
90 | flags |= SSL_VERIFY_CLIENT_ONCE; | ||
91 | } | ||
92 | |||
93 | SSL_CTX_set_verify(ctx, flags, verify_callback); | ||
94 | |||
95 | if(vc_store->flags & VC_X509S_USE_CERTIFICATE) { | ||
96 | if(vc_store->use_certfile) | ||
97 | SSL_CTX_use_certificate_chain_file(ctx, vc_store->use_certfile); | ||
98 | else { | ||
99 | SSL_CTX_use_certificate(ctx, | ||
100 | sk_X509_value(vc_store->use_certs, 0)); | ||
101 | for(i=0,n=sk_X509_num(vc_store->use_certs); i<n; i++) | ||
102 | SSL_CTX_add_extra_chain_cert(ctx, | ||
103 | sk_X509_value(vc_store->use_certs, i)); | ||
104 | } | ||
105 | |||
106 | SSL_CTX_set_default_passwd_cb(ctx, vc_store->askpass_callback); | ||
107 | |||
108 | if(vc_store->use_keyfile) { | ||
109 | r=SSL_CTX_use_PrivateKey_file(ctx, vc_store->use_keyfile, | ||
110 | SSL_FILETYPE_PEM); | ||
111 | } else if(vc_store->use_key) | ||
112 | r=SSL_CTX_use_PrivateKey(ctx, vc_store->use_key); | ||
113 | |||
114 | if(r!=1) | ||
115 | VC_SETCERT_ERR_EXIT(store, ctx, "Load private key failed"); | ||
116 | |||
117 | } | ||
118 | |||
119 | SSL_CTX_set_app_data(ctx, vc_store); | ||
120 | return(ctx); | ||
121 | } | ||
122 | |||
123 | #define VC_CONNSSL_ERR_EXIT(_cx, cx, cn) do { \ | ||
124 | snprintf(tmpstr, TMPSTRSIZE, "[SSL ERROR] %s", \ | ||
125 | ERR_error_string (ERR_get_error (), NULL)); \ | ||
126 | writecf(FS_ERR, tmpstr); \ | ||
127 | if(cn) BIO_free_all(cn); \ | ||
128 | if(*cx) SSL_CTX_free(*cx); \ | ||
129 | if(_cx) *cx = 0; \ | ||
130 | return(0); \ | ||
131 | } while(0) | ||
132 | |||
133 | BIO * vc_connect_ssl(char *host, int port, vc_x509store_t *vc_store, | ||
134 | SSL_CTX **ctx) | ||
135 | { | ||
136 | BIO *conn = NULL; | ||
137 | int _ctx = 0; | ||
138 | |||
139 | if(*ctx) { | ||
140 | CRYPTO_add( &((*ctx)->references), 1, CRYPTO_LOCK_SSL_CTX ); | ||
141 | if( vc_store && vc_store != SSL_CTX_get_app_data(*ctx) ) { | ||
142 | SSL_CTX_set_cert_store(*ctx, vc_x509store_create(vc_store)); | ||
143 | SSL_CTX_set_app_data(*ctx, vc_store); | ||
144 | } | ||
145 | } else { | ||
146 | *ctx = vc_create_sslctx(vc_store); | ||
147 | _ctx = 1; | ||
148 | } | ||
149 | |||
150 | if( !(conn = BIO_new_ssl_connect(*ctx)) ) | ||
151 | VC_CONNSSL_ERR_EXIT(_ctx, ctx, conn); | ||
152 | |||
153 | BIO_set_conn_hostname(conn, host); | ||
154 | BIO_set_conn_int_port(conn, &port); | ||
155 | |||
156 | fflush(stdout); | ||
157 | if(BIO_do_connect(conn) <= 0) | ||
158 | VC_CONNSSL_ERR_EXIT(_ctx, ctx, conn); | ||
159 | |||
160 | if(_ctx) | ||
161 | SSL_CTX_free(*ctx); | ||
162 | |||
163 | return(conn); | ||
164 | } | ||
165 | |||
166 | #define VC_CONN_ERR_EXIT(cn) do { \ | ||
167 | snprintf(tmpstr, TMPSTRSIZE, "[SSL ERROR] %s", \ | ||
168 | ERR_error_string(ERR_get_error(), NULL)); \ | ||
169 | if(ERR_get_error()) \ | ||
170 | writecf(FS_ERR, tmpstr); \ | ||
171 | if(cn) BIO_free_all(cn); \ | ||
172 | return(NULL); \ | ||
173 | } while(0) | ||
174 | |||
175 | #define VC_VERIFICATION_ERR_EXIT(cn, err) do { \ | ||
176 | snprintf(tmpstr, TMPSTRSIZE, \ | ||
177 | "[SSL ERROR] certificate verify failed: %s", err); \ | ||
178 | writecf(FS_ERR, tmpstr); \ | ||
179 | if(cn && !ignore_ssl) { BIO_free_all(cn); return(NULL); } \ | ||
180 | } while(0) | ||
181 | |||
182 | BIO * vc_connect( char *host, int port, int use_ssl, | ||
183 | vc_x509store_t *vc_store, SSL_CTX **ctx) | ||
184 | { | ||
185 | BIO *conn = NULL; | ||
186 | SSL *ssl = NULL; | ||
187 | |||
188 | if(use_ssl) { | ||
189 | if( !(conn = vc_connect_ssl(host, port, vc_store, ctx)) ) | ||
190 | VC_CONN_ERR_EXIT(conn); | ||
191 | |||
192 | |||
193 | BIO_get_ssl(conn, &ssl); | ||
194 | if(!vc_verify_cert_hostname(SSL_get_peer_certificate(ssl), host)) | ||
195 | VC_VERIFICATION_ERR_EXIT(conn, "Hostname does not match!"); | ||
196 | |||
197 | return(conn); | ||
198 | } | ||
199 | |||
200 | *ctx = 0; | ||
201 | |||
202 | if( !(conn = BIO_new_connect(host)) ) | ||
203 | VC_CONN_ERR_EXIT(conn); | ||
204 | |||
205 | BIO_set_conn_int_port(conn, &port); | ||
206 | |||
207 | if(BIO_do_connect(conn) <= 0) | ||
208 | VC_CONN_ERR_EXIT(conn); | ||
209 | |||
210 | return(conn); | ||
211 | } | ||
212 | |||
213 | int vc_verify_cert_hostname(X509 *cert, char *host) | ||
214 | { | ||
215 | |||
216 | int i = 0; | ||
217 | int j = 0; | ||
218 | int n = 0; | ||
219 | int extcount = 0; | ||
220 | int ok = 0; | ||
221 | |||
222 | X509_NAME *subj = NULL; | ||
223 | const char *extstr = NULL; | ||
224 | CONF_VALUE *nval = NULL; | ||
225 | unsigned char *data = NULL; | ||
226 | X509_EXTENSION *ext = NULL; | ||
227 | X509V3_EXT_METHOD *meth = NULL; | ||
228 | STACK_OF(CONF_VALUE) *val = NULL; | ||
229 | |||
230 | char name[256]; | ||
231 | memset(&name, 0, sizeof(name)); | ||
232 | |||
233 | if((extcount = X509_get_ext_count(cert)) > 0) { | ||
234 | |||
235 | for(i=0; !ok && i < extcount; i++) { | ||
236 | |||
237 | meth = NULL; | ||
238 | |||
239 | ext = X509_get_ext(cert, i); | ||
240 | extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext))); | ||
241 | |||
242 | if(!strcasecmp(extstr, "subjectAltName")) { | ||
243 | |||
244 | if( !(meth = X509V3_EXT_get(ext)) ) | ||
245 | break; | ||
246 | |||
247 | if( !(meth->d2i) ) | ||
248 | break; | ||
249 | |||
250 | data = ext->value->data; | ||
251 | |||
252 | val = meth->i2v(meth, meth->d2i(0, &data, ext->value->length), 0); | ||
253 | for( j=0, n=sk_CONF_VALUE_num(val); j<n; j++ ) { | ||
254 | nval = sk_CONF_VALUE_value(val, j); | ||
255 | if( !strcasecmp(nval->name, "DNS") && | ||
256 | !strcasecmp(nval->value, host) ) { | ||
257 | ok = 1; | ||
258 | break; | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | |||
265 | if( !ok && (subj = X509_get_subject_name(cert)) && | ||
266 | X509_NAME_get_text_by_NID(subj, NID_commonName, | ||
267 | name, sizeof(name)) > 0 ) { | ||
268 | name[sizeof(name)-1] = '\0'; | ||
269 | if(!strcasecmp(name, host)) | ||
270 | ok = 1; | ||
271 | } | ||
272 | |||
273 | //printf("[*] vc_verify_cert_hostname() return: %d\n", ok); | ||
274 | return(ok); | ||
275 | } | ||
276 | |||
277 | int vc_verify_cert(X509 *cert, vc_x509store_t *vc_store) | ||
278 | { | ||
279 | int result = -1; | ||
280 | X509_STORE *store = NULL; | ||
281 | X509_STORE_CTX *ctx = NULL; | ||
282 | |||
283 | if( !(store = vc_x509store_create(vc_store)) ) | ||
284 | return(result); | ||
285 | |||
286 | if( (ctx = X509_STORE_CTX_new()) != 0 ) { | ||
287 | if(X509_STORE_CTX_init(ctx, store, cert, 0) == 1) | ||
288 | result = (X509_verify_cert(ctx) == 1); | ||
289 | X509_STORE_CTX_free(ctx); | ||
290 | } | ||
291 | |||
292 | X509_STORE_free(store); | ||
293 | return(result); | ||
294 | } | ||
295 | |||
296 | #define VC_STORE_ERR_EXIT(s) do { \ | ||
297 | fprintf(stderr, "[E] SSL_STORE: %s\n", ERR_error_string (ERR_get_error (), NULL)); \ | ||
298 | if(s) X509_STORE_free(s); \ | ||
299 | return(0); \ | ||
300 | } while(0) | ||
301 | |||
302 | X509_STORE *vc_x509store_create(vc_x509store_t *vc_store) | ||
303 | { | ||
304 | int i = 0; | ||
305 | int n = 0; | ||
306 | X509_STORE *store = NULL; | ||
307 | X509_LOOKUP *lookup = NULL; | ||
308 | |||
309 | store = X509_STORE_new(); | ||
310 | |||
311 | if(vc_store->callback) | ||
312 | X509_STORE_set_verify_cb_func(store, vc_store->callback); | ||
313 | else | ||
314 | X509_STORE_set_verify_cb_func(store, vc_verify_callback); | ||
315 | |||
316 | if( !(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) ) | ||
317 | VC_STORE_ERR_EXIT(store); | ||
318 | |||
319 | if(!vc_store->cafile) { | ||
320 | if( !(vc_store->flags & VC_X509S_NODEF_CAFILE) ) | ||
321 | X509_LOOKUP_load_file(lookup, 0, X509_FILETYPE_DEFAULT); | ||
322 | } else if( !X509_LOOKUP_load_file(lookup, vc_store->cafile, | ||
323 | X509_FILETYPE_PEM) ) | ||
324 | VC_STORE_ERR_EXIT(store); | ||
325 | |||
326 | if(vc_store->crlfile) { | ||
327 | if( !X509_load_crl_file(lookup, vc_store->crlfile, | ||
328 | X509_FILETYPE_PEM) ) | ||
329 | VC_STORE_ERR_EXIT(store); | ||
330 | |||
331 | X509_STORE_set_flags( store, | ||
332 | X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL ); | ||
333 | } | ||
334 | |||
335 | if( !(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir())) ) | ||
336 | VC_STORE_ERR_EXIT(store); | ||
337 | |||
338 | if( !vc_store->capath ) { | ||
339 | if( !(vc_store->flags & VC_X509S_NODEF_CAPATH) ) | ||
340 | X509_LOOKUP_add_dir(lookup, 0, X509_FILETYPE_DEFAULT); | ||
341 | } else if( !X509_LOOKUP_add_dir(lookup, vc_store->capath, | ||
342 | X509_FILETYPE_PEM) ) | ||
343 | VC_STORE_ERR_EXIT(store); | ||
344 | |||
345 | for( i=0, n=sk_X509_num(vc_store->certs); i<n; i++) | ||
346 | if( !X509_STORE_add_cert(store, sk_X509_value(vc_store->certs, i)) ) | ||
347 | VC_STORE_ERR_EXIT(store); | ||
348 | |||
349 | for( i=0, n=sk_X509_CRL_num(vc_store->crls); i<n; i++) | ||
350 | if( !X509_STORE_add_crl(store, | ||
351 | sk_X509_CRL_value(vc_store->crls, i)) ) | ||
352 | VC_STORE_ERR_EXIT(store); | ||
353 | |||
354 | return(store); | ||
355 | } | ||
356 | |||
357 | int vc_verify_callback(int ok, X509_STORE_CTX *store) | ||
358 | { | ||
359 | if(!ok) { | ||
360 | /* XXX handle action/abort */ | ||
361 | if(!ignore_ssl) | ||
362 | snprintf(tmpstr, TMPSTRSIZE, "[SSL ERROR] %s", | ||
363 | X509_verify_cert_error_string(store->error)); | ||
364 | else | ||
365 | snprintf(tmpstr, TMPSTRSIZE, "[SSL ERROR] %s (ignored)", | ||
366 | X509_verify_cert_error_string(store->error)); | ||
367 | |||
368 | writecf(FS_ERR, tmpstr); | ||
369 | ok = ignore_ssl; | ||
370 | } | ||
371 | return(ok); | ||
372 | } | ||
373 | |||
374 | void vc_x509store_setflags(vc_x509store_t *store, int flags) | ||
375 | { | ||
376 | store->flags |= flags; | ||
377 | } | ||
378 | |||
379 | void vc_x509store_setignssl(vc_x509store_t *store, int ignore) | ||
380 | { | ||
381 | store->ignore_ssl |= ignore; | ||
382 | ignore_ssl = ignore; | ||
383 | } | ||
384 | |||
385 | void vc_x509store_clearflags(vc_x509store_t *store, int flags) | ||
386 | { | ||
387 | store->flags &= ~flags; | ||
388 | } | ||
389 | |||
390 | void vc_x509store_setcb(vc_x509store_t *store, | ||
391 | vc_x509verify_cb_t callback) | ||
392 | { | ||
393 | store->callback = callback; | ||
394 | } | ||
395 | |||
396 | void vc_x509store_set_pkeycb(vc_x509store_t *store, | ||
397 | vc_askpass_cb_t callback) | ||
398 | { | ||
399 | store->askpass_callback = callback; | ||
400 | } | ||
401 | |||
402 | void vc_x509store_addcert(vc_x509store_t *store, X509 *cert) | ||
403 | { | ||
404 | sk_X509_push(store->certs, cert); | ||
405 | } | ||
406 | |||
407 | void vc_x509store_setcafile(vc_x509store_t *store, char *file) | ||
408 | { | ||
409 | if( store->cafile) free(store->cafile); | ||
410 | store->cafile = ( file ? strdup(file) : 0 ); | ||
411 | } | ||
412 | |||
413 | void vc_x509store_setcapath(vc_x509store_t *store, char *path) | ||
414 | { | ||
415 | if( store->capath) free(store->capath); | ||
416 | store->capath = ( path ? strdup(path) : 0 ); | ||
417 | } | ||
418 | |||
419 | void vc_x509store_setcrlfile(vc_x509store_t *store, char *file) | ||
420 | { | ||
421 | if( store->crlfile) free(store->crlfile); | ||
422 | store->crlfile = ( file ? strdup(file) : 0 ); | ||
423 | } | ||
424 | |||
425 | void vc_x509store_setkeyfile(vc_x509store_t *store, char *file) | ||
426 | { | ||
427 | if( store->use_keyfile) free(store->use_keyfile); | ||
428 | store->use_keyfile = ( file ? strdup(file) : 0 ); | ||
429 | } | ||
430 | |||
431 | void vc_x509store_setcertfile(vc_x509store_t *store, char *file) | ||
432 | { | ||
433 | if( store->use_certfile) free(store->use_certfile); | ||
434 | store->use_certfile = ( file ? strdup(file) : 0 ); | ||
435 | } | ||
436 | |||
437 | |||
438 | void vc_init_x509store(vc_x509store_t *s) | ||
439 | { | ||
440 | s->cafile = NULL; | ||
441 | s->capath = NULL; | ||
442 | s->crlfile = NULL; | ||
443 | s->callback = NULL; | ||
444 | s->askpass_callback = NULL; | ||
445 | s->certs = sk_X509_new_null(); | ||
446 | s->crls = sk_X509_CRL_new_null(); | ||
447 | s->use_certfile = NULL; | ||
448 | s->use_certs = sk_X509_new_null(); | ||
449 | s->use_keyfile = NULL; | ||
450 | s->use_key = NULL; | ||
451 | s->flags = 0; | ||
452 | s->ignore_ssl = 0; | ||
453 | } | ||
454 | |||
455 | void vc_cleanup_x509store(vc_x509store_t *s) | ||
456 | { | ||
457 | if(s->cafile) free(s->cafile); | ||
458 | if(s->capath) free(s->capath); | ||
459 | if(s->crlfile) free(s->crlfile); | ||
460 | if(s->use_certfile) free(s->use_certfile); | ||
461 | if(s->use_keyfile) free(s->use_keyfile); | ||
462 | if(s->use_key) free(s->use_key); | ||
463 | sk_X509_free(s->certs); | ||
464 | sk_X509_free(s->crls); | ||
465 | sk_X509_free(s->use_certs); | ||
466 | s->ignore_ssl = 0; | ||
467 | } | ||