Mercurial > dovecot > core-2.2
comparison src/lib-ssl-iostream/iostream-openssl-context.c @ 12616:bd23d4e10fa1
Added lib-ssl-iostream for handling SSL connections more easily.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 31 Jan 2011 18:40:27 +0200 |
parents | |
children | 733ac4aba089 |
comparison
equal
deleted
inserted
replaced
12615:3dde816d945d | 12616:bd23d4e10fa1 |
---|---|
1 /* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ | |
2 | |
3 #include "lib.h" | |
4 #include "safe-memset.h" | |
5 #include "iostream-openssl.h" | |
6 | |
7 #include <openssl/crypto.h> | |
8 #include <openssl/x509.h> | |
9 #include <openssl/pem.h> | |
10 #include <openssl/ssl.h> | |
11 #include <openssl/err.h> | |
12 #include <openssl/rand.h> | |
13 | |
14 struct ssl_iostream_password_context { | |
15 const char *password; | |
16 const char *key_source; | |
17 }; | |
18 | |
19 static bool ssl_global_initialized = FALSE; | |
20 int dovecot_ssl_extdata_index; | |
21 | |
22 static void ssl_iostream_init_global(void); | |
23 | |
24 const char *ssl_iostream_error(void) | |
25 { | |
26 unsigned long err; | |
27 char *buf; | |
28 size_t err_size = 256; | |
29 | |
30 err = ERR_get_error(); | |
31 if (err == 0) { | |
32 if (errno != 0) | |
33 return strerror(errno); | |
34 return "Unknown error"; | |
35 } | |
36 if (ERR_GET_REASON(err) == ERR_R_MALLOC_FAILURE) | |
37 i_fatal_status(FATAL_OUTOFMEM, "OpenSSL malloc() failed"); | |
38 | |
39 buf = t_malloc(err_size); | |
40 buf[err_size-1] = '\0'; | |
41 ERR_error_string_n(err, buf, err_size-1); | |
42 return buf; | |
43 } | |
44 | |
45 const char *ssl_iostream_key_load_error(void) | |
46 { | |
47 unsigned long err = ERR_peek_error(); | |
48 | |
49 if (ERR_GET_LIB(err) == ERR_LIB_X509 && | |
50 ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH) | |
51 return "Key is for a different cert than ssl_cert"; | |
52 else | |
53 return ssl_iostream_error(); | |
54 } | |
55 | |
56 static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED, | |
57 int is_export ATTR_UNUSED, int keylength) | |
58 { | |
59 return RSA_generate_key(keylength, RSA_F4, NULL, NULL); | |
60 } | |
61 | |
62 static DH *ssl_tmp_dh_callback(SSL *ssl ATTR_UNUSED, | |
63 int is_export, int keylength) | |
64 { | |
65 struct ssl_iostream *ssl_io; | |
66 | |
67 ssl_io = SSL_get_ex_data(ssl, dovecot_ssl_extdata_index); | |
68 /* Well, I'm not exactly sure why the logic in here is this. | |
69 It's the same as in Postfix, so it can't be too wrong. */ | |
70 if (is_export && keylength == 512 && ssl_io->ctx->dh_512 != NULL) | |
71 return ssl_io->ctx->dh_512; | |
72 else | |
73 return ssl_io->ctx->dh_1024; | |
74 } | |
75 | |
76 static int | |
77 pem_password_callback(char *buf, int size, int rwflag ATTR_UNUSED, | |
78 void *userdata) | |
79 { | |
80 struct ssl_iostream_password_context *ctx = userdata; | |
81 | |
82 if (ctx->password == NULL) { | |
83 i_error("%s: SSL private key file is password protected, " | |
84 "but password isn't given", ctx->key_source); | |
85 return 0; | |
86 } | |
87 | |
88 if (i_strocpy(buf, userdata, size) < 0) { | |
89 i_error("%s: SSL private key password is too long", | |
90 ctx->key_source); | |
91 return 0; | |
92 } | |
93 return strlen(buf); | |
94 } | |
95 | |
96 int ssl_iostream_load_key(const struct ssl_iostream_settings *set, | |
97 const char *key_source, EVP_PKEY **pkey_r) | |
98 { | |
99 struct ssl_iostream_password_context ctx; | |
100 EVP_PKEY *pkey; | |
101 BIO *bio; | |
102 char *key; | |
103 | |
104 key = t_strdup_noconst(set->key); | |
105 bio = BIO_new_mem_buf(key, strlen(key)); | |
106 if (bio == NULL) { | |
107 i_error("BIO_new_mem_buf() failed: %s", ssl_iostream_error()); | |
108 safe_memset(key, 0, strlen(key)); | |
109 return -1; | |
110 } | |
111 | |
112 ctx.password = set->key_password; | |
113 ctx.key_source = key_source; | |
114 | |
115 pkey = PEM_read_bio_PrivateKey(bio, NULL, pem_password_callback, &ctx); | |
116 if (pkey == NULL) { | |
117 i_error("%s: Couldn't parse private SSL key: %s", | |
118 key_source, ssl_iostream_error()); | |
119 } | |
120 BIO_free(bio); | |
121 | |
122 safe_memset(key, 0, strlen(key)); | |
123 *pkey_r = pkey; | |
124 return pkey == NULL ? -1 : 0; | |
125 } | |
126 | |
127 static int | |
128 ssl_iostream_ctx_use_key(struct ssl_iostream_context *ctx, | |
129 const struct ssl_iostream_settings *set) | |
130 { | |
131 EVP_PKEY *pkey; | |
132 int ret = 0; | |
133 | |
134 if (ssl_iostream_load_key(set, ctx->source, &pkey) < 0) | |
135 return -1; | |
136 if (!SSL_CTX_use_PrivateKey(ctx->ssl_ctx, pkey)) { | |
137 i_error("%s: Can't load SSL private key: %s", | |
138 ctx->source, ssl_iostream_key_load_error()); | |
139 ret = -1; | |
140 } | |
141 EVP_PKEY_free(pkey); | |
142 return ret; | |
143 } | |
144 | |
145 static bool is_pem_key(const char *cert) | |
146 { | |
147 return strstr(cert, "PRIVATE KEY---") != NULL; | |
148 } | |
149 | |
150 const char *ssl_iostream_get_use_certificate_error(const char *cert) | |
151 { | |
152 unsigned long err; | |
153 | |
154 err = ERR_peek_error(); | |
155 if (ERR_GET_LIB(err) != ERR_LIB_PEM || | |
156 ERR_GET_REASON(err) != PEM_R_NO_START_LINE) | |
157 return ssl_iostream_error(); | |
158 else if (is_pem_key(cert)) { | |
159 return "The file contains a private key " | |
160 "(you've mixed ssl_cert and ssl_key settings)"; | |
161 } else { | |
162 return "There is no certificate."; | |
163 } | |
164 } | |
165 | |
166 static int ssl_ctx_use_certificate_chain(SSL_CTX *ctx, const char *cert) | |
167 { | |
168 /* mostly just copy&pasted from SSL_CTX_use_certificate_chain_file() */ | |
169 BIO *in; | |
170 X509 *x; | |
171 int ret = 0; | |
172 | |
173 in = BIO_new_mem_buf(t_strdup_noconst(cert), strlen(cert)); | |
174 if (in == NULL) | |
175 i_fatal("BIO_new_mem_buf() failed"); | |
176 | |
177 x = PEM_read_bio_X509(in, NULL, NULL, NULL); | |
178 if (x == NULL) | |
179 goto end; | |
180 | |
181 ret = SSL_CTX_use_certificate(ctx, x); | |
182 if (ERR_peek_error() != 0) | |
183 ret = 0; | |
184 | |
185 if (ret != 0) { | |
186 /* If we could set up our certificate, now proceed to | |
187 * the CA certificates. | |
188 */ | |
189 X509 *ca; | |
190 int r; | |
191 unsigned long err; | |
192 | |
193 while ((ca = PEM_read_bio_X509(in,NULL,NULL,NULL)) != NULL) { | |
194 r = SSL_CTX_add_extra_chain_cert(ctx, ca); | |
195 if (!r) { | |
196 X509_free(ca); | |
197 ret = 0; | |
198 goto end; | |
199 } | |
200 } | |
201 /* When the while loop ends, it's usually just EOF. */ | |
202 err = ERR_peek_last_error(); | |
203 if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) | |
204 ERR_clear_error(); | |
205 else | |
206 ret = 0; /* some real error */ | |
207 } | |
208 | |
209 end: | |
210 if (x != NULL) X509_free(x); | |
211 BIO_free(in); | |
212 return ret; | |
213 } | |
214 | |
215 static int load_ca(X509_STORE *store, const char *ca, | |
216 STACK_OF(X509_NAME) **xnames_r) | |
217 { | |
218 /* mostly just copy&pasted from X509_load_cert_crl_file() */ | |
219 STACK_OF(X509_INFO) *inf; | |
220 STACK_OF(X509_NAME) *xnames; | |
221 X509_INFO *itmp; | |
222 X509_NAME *xname; | |
223 BIO *bio; | |
224 int i; | |
225 | |
226 bio = BIO_new_mem_buf(t_strdup_noconst(ca), strlen(ca)); | |
227 if (bio == NULL) | |
228 i_fatal("BIO_new_mem_buf() failed"); | |
229 inf = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); | |
230 BIO_free(bio); | |
231 | |
232 if (inf == NULL) | |
233 return -1; | |
234 | |
235 xnames = sk_X509_NAME_new_null(); | |
236 if (xnames == NULL) | |
237 i_fatal("sk_X509_NAME_new_null() failed"); | |
238 for(i = 0; i < sk_X509_INFO_num(inf); i++) { | |
239 itmp = sk_X509_INFO_value(inf, i); | |
240 if(itmp->x509) { | |
241 X509_STORE_add_cert(store, itmp->x509); | |
242 xname = X509_get_subject_name(itmp->x509); | |
243 if (xname != NULL) | |
244 xname = X509_NAME_dup(xname); | |
245 if (xname != NULL) | |
246 sk_X509_NAME_push(xnames, xname); | |
247 } | |
248 if(itmp->crl) | |
249 X509_STORE_add_crl(store, itmp->crl); | |
250 } | |
251 sk_X509_INFO_pop_free(inf, X509_INFO_free); | |
252 *xnames_r = xnames; | |
253 return 0; | |
254 } | |
255 | |
256 static int | |
257 ssl_iostream_ctx_verify_remote_cert(struct ssl_iostream_context *ctx, | |
258 STACK_OF(X509_NAME) *ca_names) | |
259 { | |
260 #if OPENSSL_VERSION_NUMBER >= 0x00907000L | |
261 X509_STORE *store; | |
262 | |
263 store = SSL_CTX_get_cert_store(ctx->ssl_ctx); | |
264 X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | | |
265 X509_V_FLAG_CRL_CHECK_ALL); | |
266 #endif | |
267 | |
268 SSL_CTX_set_client_CA_list(ctx->ssl_ctx, ca_names); | |
269 return 0; | |
270 } | |
271 | |
272 static struct ssl_iostream_settings * | |
273 ssl_iostream_settings_dup(pool_t pool, | |
274 const struct ssl_iostream_settings *old_set) | |
275 { | |
276 struct ssl_iostream_settings *new_set; | |
277 | |
278 new_set = p_new(pool, struct ssl_iostream_settings, 1); | |
279 new_set->cipher_list = p_strdup(pool, old_set->cipher_list); | |
280 new_set->cert = p_strdup(pool, old_set->cert); | |
281 new_set->key = p_strdup(pool, old_set->key); | |
282 new_set->key_password = p_strdup(pool, old_set->key_password); | |
283 | |
284 new_set->verbose = old_set->verbose; | |
285 return new_set; | |
286 } | |
287 | |
288 static int | |
289 ssl_iostream_context_set(struct ssl_iostream_context *ctx, | |
290 const struct ssl_iostream_settings *set) | |
291 { | |
292 X509_STORE *store; | |
293 STACK_OF(X509_NAME) *xnames = NULL; | |
294 | |
295 ctx->set = ssl_iostream_settings_dup(ctx->pool, set); | |
296 if (set->cipher_list != NULL && | |
297 !SSL_CTX_set_cipher_list(ctx->ssl_ctx, set->cipher_list)) { | |
298 i_error("%s: Can't set cipher list to '%s': %s", | |
299 ctx->source, set->cipher_list, | |
300 ssl_iostream_error()); | |
301 return -1; | |
302 } | |
303 | |
304 if (set->cert != NULL && | |
305 ssl_ctx_use_certificate_chain(ctx->ssl_ctx, set->cert) < 0) { | |
306 i_error("%s: Can't load SSL certificate: %s", ctx->source, | |
307 ssl_iostream_get_use_certificate_error(set->cert)); | |
308 } | |
309 if (set->key != NULL) { | |
310 if (ssl_iostream_ctx_use_key(ctx, set) < 0) | |
311 return -1; | |
312 } | |
313 | |
314 /* set trusted CA certs */ | |
315 if (!set->verify_remote_cert) { | |
316 /* no CA */ | |
317 } else if (set->ca != NULL) { | |
318 store = SSL_CTX_get_cert_store(ctx->ssl_ctx); | |
319 if (load_ca(store, set->ca, &xnames) < 0) { | |
320 i_error("%s: Couldn't parse ssl_ca: %s", ctx->source, | |
321 ssl_iostream_error()); | |
322 return -1; | |
323 } | |
324 if (ssl_iostream_ctx_verify_remote_cert(ctx, xnames) < 0) | |
325 return -1; | |
326 } else if (set->ca_dir != NULL) { | |
327 if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, NULL, | |
328 set->ca_dir)) { | |
329 i_error("%s: Can't load CA certs from directory %s: %s", | |
330 ctx->source, set->ca_dir, ssl_iostream_error()); | |
331 return -1; | |
332 } | |
333 } else { | |
334 i_error("%s: Can't verify remote certs without CA", | |
335 ctx->source); | |
336 return -1; | |
337 } | |
338 | |
339 if (set->cert_username_field != NULL) { | |
340 ctx->username_nid = OBJ_txt2nid(set->cert_username_field); | |
341 if (ctx->username_nid == NID_undef) { | |
342 i_error("%s: Invalid cert_username_field: %s", | |
343 ctx->source, set->cert_username_field); | |
344 } | |
345 } | |
346 return 0; | |
347 } | |
348 | |
349 static int | |
350 ssl_iostream_context_init_common(struct ssl_iostream_context *ctx, | |
351 const char *source, | |
352 const struct ssl_iostream_settings *set) | |
353 { | |
354 ctx->pool = pool_alloconly_create("ssl iostream context", 4096); | |
355 ctx->source = p_strdup(ctx->pool, source); | |
356 | |
357 SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); | |
358 if (SSL_CTX_need_tmp_RSA(ctx->ssl_ctx)) | |
359 SSL_CTX_set_tmp_rsa_callback(ctx->ssl_ctx, ssl_gen_rsa_key); | |
360 SSL_CTX_set_tmp_dh_callback(ctx->ssl_ctx, ssl_tmp_dh_callback); | |
361 | |
362 return ssl_iostream_context_set(ctx, set); | |
363 } | |
364 | |
365 int ssl_iostream_context_init_client(const char *source, | |
366 const struct ssl_iostream_settings *set, | |
367 struct ssl_iostream_context **ctx_r) | |
368 { | |
369 struct ssl_iostream_context *ctx; | |
370 SSL_CTX *ssl_ctx; | |
371 | |
372 ssl_iostream_init_global(); | |
373 if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { | |
374 i_error("SSL_CTX_new() failed: %s", ssl_iostream_error()); | |
375 return -1; | |
376 } | |
377 | |
378 ctx = i_new(struct ssl_iostream_context, 1); | |
379 ctx->ssl_ctx = ssl_ctx; | |
380 ctx->client_ctx = TRUE; | |
381 if (ssl_iostream_context_init_common(ctx, source, set) < 0) { | |
382 ssl_iostream_context_deinit(&ctx); | |
383 return -1; | |
384 } | |
385 *ctx_r = ctx; | |
386 return 0; | |
387 } | |
388 | |
389 int ssl_iostream_context_init_server(const char *source, | |
390 const struct ssl_iostream_settings *set, | |
391 struct ssl_iostream_context **ctx_r) | |
392 { | |
393 struct ssl_iostream_context *ctx; | |
394 SSL_CTX *ssl_ctx; | |
395 | |
396 ssl_iostream_init_global(); | |
397 if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) { | |
398 i_error("SSL_CTX_new() failed: %s", ssl_iostream_error()); | |
399 return -1; | |
400 } | |
401 | |
402 ctx = i_new(struct ssl_iostream_context, 1); | |
403 ctx->ssl_ctx = ssl_ctx; | |
404 if (ssl_iostream_context_init_common(ctx, source, set) < 0) { | |
405 ssl_iostream_context_deinit(&ctx); | |
406 return -1; | |
407 } | |
408 *ctx_r = ctx; | |
409 return 0; | |
410 } | |
411 | |
412 void ssl_iostream_context_deinit(struct ssl_iostream_context **_ctx) | |
413 { | |
414 struct ssl_iostream_context *ctx = *_ctx; | |
415 | |
416 *_ctx = NULL; | |
417 SSL_CTX_free(ctx->ssl_ctx); | |
418 ssl_iostream_context_free_params(ctx); | |
419 pool_unref(&ctx->pool); | |
420 i_free(ctx); | |
421 } | |
422 | |
423 static void ssl_iostream_deinit_global(void) | |
424 { | |
425 EVP_cleanup(); | |
426 ERR_free_strings(); | |
427 } | |
428 | |
429 static void ssl_iostream_init_global(void) | |
430 { | |
431 static char dovecot[] = "dovecot"; | |
432 unsigned char buf; | |
433 | |
434 if (ssl_global_initialized) | |
435 return; | |
436 | |
437 atexit(ssl_iostream_deinit_global); | |
438 ssl_global_initialized = TRUE; | |
439 SSL_library_init(); | |
440 SSL_load_error_strings(); | |
441 | |
442 dovecot_ssl_extdata_index = | |
443 SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL); | |
444 | |
445 /* PRNG initialization might want to use /dev/urandom, make sure it | |
446 does it before chrooting. We might not have enough entropy at | |
447 the first try, so this function may fail. It's still been | |
448 initialized though. */ | |
449 (void)RAND_bytes(&buf, 1); | |
450 } |