Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117750173
tls.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
33 KB
Referenced Files
None
Subscribers
None
tls.c
View Options
/* tls.c - STARTTLS helper functions for imapd
* Tim Martin
* 9/21/99
*
* Based upon Lutz Jaenicke's TLS patches for postfix
*
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
* NAME
* tls
* SUMMARY
* interface to openssl routines
* SYNOPSIS
* #include <tls.h>
*
* DESCRIPTION
* This module is the interface between Cyrus Imapd and the OpenSSL library.
* As of now only one filedescriptor can be handled, so only one
* TLS channel can be open at a time.
*
* tls_init_serverengine() is called once when the server is started
* in order to initialize as much of the TLS stuff as possible.
* The certificate handling is also decided during the setup phase,
* so that a peer specific handling is not possible.
*
* tls_start_servertls() activates the TLS feature for the
* filedescriptor selected with tls_setfd() before. We expect
* that all buffers are flushed and the TLS handshake can begin
* immediately.
*
* tls_stop_servertls() sends the "close notify" alert via
* SSL_shutdown() to the peer and resets all connection specific
* TLS data. As RFC2487 does not specify a seperate shutdown, it
* is supposed that the underlying TCP connection is shut down
* immediately afterwards, so we don't care about additional data
* coming through the channel.
*
* Once the TLS connection is initiated, information about the TLS
* state is available:
* tls_protocol holds the protocol name (SSLv2, SSLv3, TLSv1),
* tls_cipher_name the cipher name (e.g. RC4/MD5),
* tls_cipher_usebits the number of bits actually used (e.g. 40),
* tls_cipher_algbits the number of bits the algorithm is based on
* (e.g. 128).
* The last two values may be different when talking to a crippled
* - ahem - export controled peer (e.g. 40/128).
*
* xxx we need to offer a callback to do peer issuer certification.
* data that should be available for inspection:
* If the peer offered a certifcate _and_ the certificate could be
* verified successfully, part of the certificate data are available as:
* tls_peer_subject X509v3-oneline with the DN of the peer
* pfixlts_peer_CN extracted CommonName of the peer
* tls_peer_issuer X509v3-oneline with the DN of the issuer
* pfixlts_peer_CN extracted CommonName of the issuer
* tls_peer_fingerprint fingerprint of the certificate
*
*/
/* $Id: tls.c,v 1.47 2003/10/22 18:50:08 rjs3 Exp $ */
#include
<config.h>
#ifdef HAVE_SSL
/* System library. */
#include
<sys/types.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<string.h>
#include
<syslog.h>
/* OpenSSL library. */
#include
<openssl/lhash.h>
#include
<openssl/bn.h>
#include
<openssl/err.h>
#include
<openssl/pem.h>
#include
<openssl/x509.h>
#include
<openssl/ssl.h>
/* Application-specific. */
#include
"assert.h"
#include
"xmalloc.h"
#include
"tls.h"
/* Session caching/reuse stuff */
#include
"global.h"
#include
"cyrusdb.h"
#define DB (CONFIG_DB_TLS)
/* sessions are binary -> MUST use DB3 */
static
struct
db
*
sessdb
=
NULL
;
static
int
sess_dbopen
=
0
;
/* We must keep some of the info available */
static
const
char
hexcodes
[]
=
"0123456789ABCDEF"
;
enum
{
var_imapd_tls_loglevel
=
0
,
var_proxy_tls_loglevel
=
0
,
CCERT_BUFSIZ
=
256
};
static
int
verify_depth
=
5
;
static
int
verify_error
=
X509_V_OK
;
static
SSL_CTX
*
s_ctx
=
NULL
,
*
c_ctx
=
NULL
;
static
int
tls_serverengine
=
0
;
/* server engine initialized? */
static
int
tls_clientengine
=
0
;
/* client engine initialized? */
static
int
do_dump
=
0
;
/* actively dumping protocol? */
int
tls_enabled
(
void
)
{
const
char
*
val
;
val
=
config_getstring
(
IMAPOPT_TLS_CERT_FILE
);
if
(
!
val
||
!
strcasecmp
(
val
,
"disabled"
))
return
0
;
val
=
config_getstring
(
IMAPOPT_TLS_KEY_FILE
);
if
(
!
val
||
!
strcasecmp
(
val
,
"disabled"
))
return
0
;
return
1
;
}
/* taken from OpenSSL apps/s_cb.c
* tim - this seems to just be giving logging messages
*/
static
void
apps_ssl_info_callback
(
SSL
*
s
,
int
where
,
int
ret
)
{
char
*
str
;
int
w
;
if
(
var_imapd_tls_loglevel
==
0
)
return
;
w
=
where
&
~
SSL_ST_MASK
;
if
(
w
&
SSL_ST_CONNECT
)
str
=
"SSL_connect"
;
else
if
(
w
&
SSL_ST_ACCEPT
)
str
=
"SSL_accept"
;
else
str
=
"undefined"
;
if
(
where
&
SSL_CB_LOOP
)
{
if
(
tls_serverengine
&&
(
var_imapd_tls_loglevel
>=
2
))
syslog
(
LOG_DEBUG
,
"%s:%s"
,
str
,
SSL_state_string_long
(
s
));
}
else
if
(
where
&
SSL_CB_ALERT
)
{
str
=
(
where
&
SSL_CB_READ
)
?
"read"
:
"write"
;
if
((
tls_serverengine
&&
(
var_imapd_tls_loglevel
>=
2
))
||
((
ret
&
0xff
)
!=
SSL3_AD_CLOSE_NOTIFY
))
syslog
(
LOG_DEBUG
,
"SSL3 alert %s:%s:%s"
,
str
,
SSL_alert_type_string_long
(
ret
),
SSL_alert_desc_string_long
(
ret
));
}
else
if
(
where
&
SSL_CB_EXIT
)
{
if
(
ret
==
0
)
syslog
(
LOG_DEBUG
,
"%s:failed in %s"
,
str
,
SSL_state_string_long
(
s
));
else
if
(
ret
<
0
)
{
syslog
(
LOG_DEBUG
,
"%s:error in %s"
,
str
,
SSL_state_string_long
(
s
));
}
}
}
/* taken from OpenSSL apps/s_cb.c
not thread safe! */
static
RSA
*
tmp_rsa_cb
(
SSL
*
s
__attribute__
((
unused
)),
int
export
__attribute__
((
unused
)),
int
keylength
)
{
static
RSA
*
rsa_tmp
=
NULL
;
if
(
rsa_tmp
==
NULL
)
{
rsa_tmp
=
RSA_generate_key
(
keylength
,
RSA_F4
,
NULL
,
NULL
);
}
return
(
rsa_tmp
);
}
/* taken from OpenSSL apps/s_cb.c */
static
int
verify_callback
(
int
ok
,
X509_STORE_CTX
*
ctx
)
{
char
buf
[
256
];
X509
*
err_cert
;
int
err
;
int
depth
;
syslog
(
LOG_ERR
,
"Doing a peer verify"
);
err_cert
=
X509_STORE_CTX_get_current_cert
(
ctx
);
err
=
X509_STORE_CTX_get_error
(
ctx
);
depth
=
X509_STORE_CTX_get_error_depth
(
ctx
);
X509_NAME_oneline
(
X509_get_subject_name
(
err_cert
),
buf
,
sizeof
(
buf
));
if
(
ok
==
0
)
{
syslog
(
LOG_ERR
,
"verify error:num=%d:%s"
,
err
,
X509_verify_cert_error_string
(
err
));
if
(
verify_depth
>=
depth
)
{
ok
=
0
;
verify_error
=
X509_V_OK
;
}
else
{
ok
=
0
;
verify_error
=
X509_V_ERR_CERT_CHAIN_TOO_LONG
;
}
}
switch
(
ctx
->
error
)
{
case
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
:
X509_NAME_oneline
(
X509_get_issuer_name
(
ctx
->
current_cert
),
buf
,
sizeof
(
buf
));
syslog
(
LOG_NOTICE
,
"issuer= %s"
,
buf
);
break
;
case
X509_V_ERR_CERT_NOT_YET_VALID
:
case
X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD
:
syslog
(
LOG_NOTICE
,
"cert not yet valid"
);
break
;
case
X509_V_ERR_CERT_HAS_EXPIRED
:
case
X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD
:
syslog
(
LOG_NOTICE
,
"cert has expired"
);
break
;
}
return
(
ok
);
}
/*
* taken from OpenSSL crypto/bio/b_dump.c, modified to save a lot of strcpy
* and strcat by Matti Aarnio.
*/
#define TRUNCATE
#define DUMP_WIDTH 16
static
int
tls_dump
(
const
char
*
s
,
int
len
)
{
int
ret
=
0
;
char
buf
[
160
+
1
];
char
*
ss
;
int
i
;
int
j
;
int
rows
;
int
trunc
;
unsigned
char
ch
;
trunc
=
0
;
#ifdef TRUNCATE
for
(;
(
len
>
0
)
&&
((
s
[
len
-
1
]
==
' '
)
||
(
s
[
len
-
1
]
==
'\0'
));
len
--
)
trunc
++
;
#endif
rows
=
(
len
/
DUMP_WIDTH
);
if
((
rows
*
DUMP_WIDTH
)
<
len
)
rows
++
;
for
(
i
=
0
;
i
<
rows
;
i
++
)
{
unsigned
int
val
;
buf
[
0
]
=
'\0'
;
/* start with empty string */
ss
=
buf
;
val
=
i
*
DUMP_WIDTH
;
assert
(
val
<=
0xFFFF
);
sprintf
(
ss
,
"%04x "
,
i
*
DUMP_WIDTH
);
ss
+=
strlen
(
ss
);
for
(
j
=
0
;
j
<
DUMP_WIDTH
;
j
++
)
{
if
(((
i
*
DUMP_WIDTH
)
+
j
)
>=
len
)
{
strcpy
(
ss
,
" "
);
}
else
{
ch
=
((
unsigned
char
)
*
((
char
*
)
(
s
)
+
i
*
DUMP_WIDTH
+
j
))
&
0xFF
;
sprintf
(
ss
,
"%02x%c"
,
ch
,
j
==
7
?
'|'
:
' '
);
ss
+=
3
;
}
}
ss
+=
strlen
(
ss
);
*
ss
+=
' '
;
for
(
j
=
0
;
j
<
DUMP_WIDTH
;
j
++
)
{
if
(((
i
*
DUMP_WIDTH
)
+
j
)
>=
len
)
break
;
ch
=
((
unsigned
char
)
*
((
char
*
)
(
s
)
+
i
*
DUMP_WIDTH
+
j
))
&
0xff
;
*
ss
+=
(((
ch
>=
' '
)
&&
(
ch
<=
'~'
))
?
ch
:
'.'
);
if
(
j
==
7
)
*
ss
+=
' '
;
}
*
ss
=
0
;
/*
* if this is the last call then update the ddt_dump thing so that
* we will move the selection point in the debug window
*/
if
(
var_imapd_tls_loglevel
>
0
)
printf
(
"%s"
,
buf
);
ret
+=
strlen
(
buf
);
}
#ifdef TRUNCATE
if
(
trunc
>
0
)
{
snprintf
(
buf
,
sizeof
(
buf
),
"%04x - <SPACES/NULS>
\n
"
,
len
+
trunc
);
if
(
var_imapd_tls_loglevel
>
0
)
printf
(
"%s"
,
buf
);
ret
+=
strlen
(
buf
);
}
#endif
return
(
ret
);
}
/*
* Set up the cert things on the server side. We do need both the
* private key (in key_file) and the cert (in cert_file).
* Both files may be identical.
*
* This function is taken from OpenSSL apps/s_cb.c
*/
static
int
set_cert_stuff
(
SSL_CTX
*
ctx
,
const
char
*
cert_file
,
const
char
*
key_file
)
{
if
(
cert_file
!=
NULL
)
{
if
(
SSL_CTX_use_certificate_file
(
ctx
,
cert_file
,
SSL_FILETYPE_PEM
)
<=
0
)
{
syslog
(
LOG_ERR
,
"unable to get certificate from '%s'"
,
cert_file
);
return
(
0
);
}
if
(
key_file
==
NULL
)
key_file
=
cert_file
;
if
(
SSL_CTX_use_PrivateKey_file
(
ctx
,
key_file
,
SSL_FILETYPE_PEM
)
<=
0
)
{
syslog
(
LOG_ERR
,
"unable to get private key from '%s'"
,
key_file
);
return
(
0
);
}
/* Now we know that a key and cert have been set against
* the SSL context */
if
(
!
SSL_CTX_check_private_key
(
ctx
))
{
syslog
(
LOG_ERR
,
"Private key does not match the certificate public key"
);
return
(
0
);
}
}
return
(
1
);
}
/*
* The new_session_cb() is called, whenever a new session has been
* negotiated and session caching is enabled. We save the session in
* a database so that we can share sessions between processes.
*/
static
int
new_session_cb
(
SSL
*
ssl
__attribute__
((
unused
)),
SSL_SESSION
*
sess
)
{
int
len
;
unsigned
char
*
data
=
NULL
,
*
asn
;
time_t
expire
;
int
ret
=
-1
;
assert
(
sess
);
if
(
!
sess_dbopen
)
return
0
;
/* find the size of the ASN1 representation of the session */
len
=
i2d_SSL_SESSION
(
sess
,
NULL
);
/*
* create the data buffer. the data is stored as:
* <expire time><ASN1 data>
*/
data
=
(
unsigned
char
*
)
xmalloc
(
sizeof
(
time_t
)
+
len
*
sizeof
(
unsigned
char
));
/* transform the session into its ASN1 representation */
if
(
data
)
{
asn
=
data
+
sizeof
(
time_t
);
len
=
i2d_SSL_SESSION
(
sess
,
&
asn
);
if
(
!
len
)
syslog
(
LOG_ERR
,
"i2d_SSL_SESSION failed"
);
}
/* set the expire time for the external cache, and prepend it to data */
expire
=
SSL_SESSION_get_time
(
sess
)
+
SSL_SESSION_get_timeout
(
sess
);
memcpy
(
data
,
&
expire
,
sizeof
(
time_t
));
if
(
data
&&
len
)
{
/* store the session in our database */
do
{
ret
=
DB
->
store
(
sessdb
,
sess
->
session_id
,
sess
->
session_id_length
,
data
,
len
+
sizeof
(
time_t
),
NULL
);
}
while
(
ret
==
CYRUSDB_AGAIN
);
}
if
(
data
)
free
(
data
);
/* log this transaction */
if
(
var_imapd_tls_loglevel
>
0
)
{
unsigned
int
i
;
char
idstr
[
SSL_MAX_SSL_SESSION_ID_LENGTH
*
2
+
1
];
for
(
i
=
0
;
i
<
sess
->
session_id_length
;
i
++
)
{
sprintf
(
idstr
+
i
*
2
,
"%02X"
,
sess
->
session_id
[
i
]);
}
syslog
(
LOG_DEBUG
,
"new TLS session: id=%s, expire=%s, status=%s"
,
idstr
,
ctime
(
&
expire
),
ret
?
"failed"
:
"ok"
);
}
return
(
ret
==
0
);
}
/*
* Function for removing a session from our database.
*/
static
void
remove_session
(
unsigned
char
*
id
,
int
idlen
)
{
int
ret
;
assert
(
id
);
assert
(
idlen
<=
SSL_MAX_SSL_SESSION_ID_LENGTH
);
if
(
!
sess_dbopen
)
return
;
do
{
ret
=
DB
->
delete
(
sessdb
,
id
,
idlen
,
NULL
,
1
);
}
while
(
ret
==
CYRUSDB_AGAIN
);
/* log this transaction */
if
(
var_imapd_tls_loglevel
>
0
)
{
int
i
;
char
idstr
[
SSL_MAX_SSL_SESSION_ID_LENGTH
*
2
+
1
];
for
(
i
=
0
;
i
<
idlen
;
i
++
)
{
sprintf
(
idstr
+
i
*
2
,
"%02X"
,
id
[
i
]);
}
syslog
(
LOG_DEBUG
,
"remove TLS session: id=%s"
,
idstr
);
}
}
/*
* The remove_session_cb() is called, whenever the SSL engine removes
* a session from the internal cache. This happens if the session is
* removed because it is expired or when a connection was not shutdown
* cleanly.
*/
static
void
remove_session_cb
(
SSL_CTX
*
ctx
__attribute__
((
unused
)),
SSL_SESSION
*
sess
)
{
assert
(
sess
);
remove_session
(
sess
->
session_id
,
sess
->
session_id_length
);
}
/*
* The get_session_cb() is only called on SSL/TLS servers with the
* session id proposed by the client. The get_session_cb() is always
* called, also when session caching was disabled. We lookup the
* session in our database in case it was stored by another process.
*/
static
SSL_SESSION
*
get_session_cb
(
SSL
*
ssl
__attribute__
((
unused
)),
unsigned
char
*
id
,
int
idlen
,
int
*
copy
)
{
int
ret
;
const
char
*
data
=
NULL
;
unsigned
char
*
asn
;
int
len
=
0
;
time_t
expire
=
0
,
now
=
time
(
0
);
SSL_SESSION
*
sess
=
NULL
;
assert
(
id
);
assert
(
idlen
<=
SSL_MAX_SSL_SESSION_ID_LENGTH
);
if
(
!
sess_dbopen
)
return
NULL
;
do
{
ret
=
DB
->
fetch
(
sessdb
,
id
,
idlen
,
&
data
,
&
len
,
NULL
);
}
while
(
ret
==
CYRUSDB_AGAIN
);
if
(
data
)
{
assert
(
len
>=
(
int
)
sizeof
(
time_t
));
/* grab the expire time */
memcpy
(
&
expire
,
data
,
sizeof
(
time_t
));
/* check if the session has expired */
if
(
expire
<
now
)
{
remove_session
(
id
,
idlen
);
}
else
{
/* transform the ASN1 representation of the session
into an SSL_SESSION object */
asn
=
(
unsigned
char
*
)
data
+
sizeof
(
time_t
);
sess
=
d2i_SSL_SESSION
(
NULL
,
&
asn
,
len
-
sizeof
(
time_t
));
if
(
!
sess
)
syslog
(
LOG_ERR
,
"d2i_SSL_SESSION failed: %m"
);
}
}
/* log this transaction */
if
(
var_imapd_tls_loglevel
>
0
)
{
int
i
;
char
idstr
[
SSL_MAX_SSL_SESSION_ID_LENGTH
*
2
+
1
];
for
(
i
=
0
;
i
<
idlen
;
i
++
)
sprintf
(
idstr
+
i
*
2
,
"%02X"
,
id
[
i
]);
syslog
(
LOG_DEBUG
,
"get TLS session: id=%s, expire=%s, status=%s"
,
idstr
,
ctime
(
&
expire
),
!
data
?
"not found"
:
expire
<
now
?
"expired"
:
"ok"
);
}
*
copy
=
0
;
return
sess
;
}
/*
* Seed the random number generator.
*/
static
int
tls_rand_init
(
void
)
{
#ifdef EGD_SOCKET
return
(
RAND_egd
(
EGD_SOCKET
));
#else
/* otherwise let OpenSSL do it internally */
return
0
;
#endif
}
/*
* This is the setup routine for the SSL server. As smtpd might be called
* more than once, we only want to do the initialization one time.
*
* The skeleton of this function is taken from OpenSSL apps/s_server.c.
* returns -1 on error
*/
/* must be called after cyrus_init */
int
tls_init_serverengine
(
const
char
*
ident
,
int
verifydepth
,
int
askcert
,
int
tlsonly
)
{
int
off
=
0
;
int
verify_flags
=
SSL_VERIFY_NONE
;
const
char
*
cipher_list
;
const
char
*
CApath
;
const
char
*
CAfile
;
const
char
*
s_cert_file
;
const
char
*
s_key_file
;
int
requirecert
;
int
timeout
;
if
(
tls_serverengine
)
return
(
0
);
/* already running */
if
(
var_imapd_tls_loglevel
>=
2
)
syslog
(
LOG_DEBUG
,
"starting TLS server engine"
);
SSL_library_init
();
SSL_load_error_strings
();
if
(
tls_rand_init
()
==
-1
)
{
syslog
(
LOG_ERR
,
"TLS server engine: cannot seed PRNG"
);
return
-1
;
}
#if 0
if (tlsonly) {
s_ctx = SSL_CTX_new(TLSv1_server_method());
} else {
s_ctx = SSL_CTX_new(SSLv23_server_method());
}
#endif
/* even if we want TLS only, we use SSLv23 server method so we can
deal with a client sending an SSLv2 greeting message */
s_ctx
=
SSL_CTX_new
(
SSLv23_server_method
());
if
(
s_ctx
==
NULL
)
{
return
(
-1
);
};
off
|=
SSL_OP_ALL
;
/* Work around all known bugs */
if
(
tlsonly
)
{
off
|=
SSL_OP_NO_SSLv2
;
off
|=
SSL_OP_NO_SSLv3
;
}
SSL_CTX_set_options
(
s_ctx
,
off
);
SSL_CTX_set_info_callback
(
s_ctx
,
(
void
(
*
)())
apps_ssl_info_callback
);
/* Don't use an internal session cache */
SSL_CTX_sess_set_cache_size
(
s_ctx
,
1
);
/* 0 is unlimited, so use 1 */
SSL_CTX_set_session_cache_mode
(
s_ctx
,
SSL_SESS_CACHE_SERVER
|
SSL_SESS_CACHE_NO_AUTO_CLEAR
|
SSL_SESS_CACHE_NO_INTERNAL_LOOKUP
);
/* Get the session timeout from the config file (in minutes) */
timeout
=
config_getint
(
IMAPOPT_TLS_SESSION_TIMEOUT
);
if
(
timeout
<
0
)
timeout
=
0
;
if
(
timeout
>
1440
)
timeout
=
1440
;
/* 24 hours max */
/* A timeout of zero disables session caching */
if
(
timeout
)
{
char
dbdir
[
1024
];
int
r
;
/* Set the context for session reuse -- use the service ident */
SSL_CTX_set_session_id_context
(
s_ctx
,
(
void
*
)
ident
,
strlen
(
ident
));
/* Set the timeout for the internal/external cache (in seconds) */
SSL_CTX_set_timeout
(
s_ctx
,
timeout
*
60
);
/* Set the callback functions for the external session cache */
SSL_CTX_sess_set_new_cb
(
s_ctx
,
new_session_cb
);
SSL_CTX_sess_set_remove_cb
(
s_ctx
,
remove_session_cb
);
SSL_CTX_sess_set_get_cb
(
s_ctx
,
get_session_cb
);
/* create the name of the db file */
strlcpy
(
dbdir
,
config_dir
,
sizeof
(
dbdir
));
strlcat
(
dbdir
,
FNAME_TLSSESSIONS
,
sizeof
(
dbdir
));
r
=
DB
->
open
(
dbdir
,
CYRUSDB_CREATE
,
&
sessdb
);
if
(
r
!=
0
)
{
syslog
(
LOG_ERR
,
"DBERROR: opening %s: %s"
,
dbdir
,
cyrusdb_strerror
(
ret
));
}
else
sess_dbopen
=
1
;
}
cipher_list
=
config_getstring
(
IMAPOPT_TLS_CIPHER_LIST
);
if
(
!
SSL_CTX_set_cipher_list
(
s_ctx
,
cipher_list
))
{
syslog
(
LOG_ERR
,
"TLS server engine: cannot load cipher list '%s'"
,
cipher_list
);
return
(
-1
);
}
CAfile
=
config_getstring
(
IMAPOPT_TLS_CA_FILE
);
CApath
=
config_getstring
(
IMAPOPT_TLS_CA_PATH
);
if
((
!
SSL_CTX_load_verify_locations
(
s_ctx
,
CAfile
,
CApath
))
||
(
!
SSL_CTX_set_default_verify_paths
(
s_ctx
)))
{
/* just a warning since this is only necessary for client auth */
syslog
(
LOG_NOTICE
,
"TLS server engine: cannot load CA data"
);
}
s_cert_file
=
config_getstring
(
IMAPOPT_TLS_CERT_FILE
);
s_key_file
=
config_getstring
(
IMAPOPT_TLS_KEY_FILE
);
if
(
!
set_cert_stuff
(
s_ctx
,
s_cert_file
,
s_key_file
))
{
syslog
(
LOG_ERR
,
"TLS server engine: cannot load cert/key data"
);
return
(
-1
);
}
SSL_CTX_set_tmp_rsa_callback
(
s_ctx
,
tmp_rsa_cb
);
verify_depth
=
verifydepth
;
if
(
askcert
!=
0
)
verify_flags
|=
SSL_VERIFY_PEER
|
SSL_VERIFY_CLIENT_ONCE
;
requirecert
=
config_getswitch
(
IMAPOPT_TLS_REQUIRE_CERT
);
if
(
requirecert
)
verify_flags
|=
SSL_VERIFY_PEER
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT
|
SSL_VERIFY_CLIENT_ONCE
;
SSL_CTX_set_verify
(
s_ctx
,
verify_flags
,
verify_callback
);
if
(
askcert
||
requirecert
)
{
if
(
CAfile
==
NULL
)
{
syslog
(
LOG_ERR
,
"TLS server engine: No CA file specified. "
"Client side certs may not work"
);
}
else
{
SSL_CTX_set_client_CA_list
(
s_ctx
,
SSL_load_client_CA_file
(
CAfile
));
}
}
tls_serverengine
=
1
;
return
(
0
);
}
/* taken from OpenSSL apps/s_cb.c */
static
long
bio_dump_cb
(
BIO
*
bio
,
int
cmd
,
const
char
*
argp
,
int
argi
,
long
argl
__attribute__
((
unused
)),
long
ret
)
{
if
(
!
do_dump
)
return
(
ret
);
if
(
cmd
==
(
BIO_CB_READ
|
BIO_CB_RETURN
))
{
printf
(
"read from %08X [%08lX] (%d bytes => %ld (0x%X))"
,
(
unsigned
int
)
bio
,
(
long
unsigned
int
)
argp
,
argi
,
ret
,
(
unsigned
int
)
ret
);
tls_dump
(
argp
,
(
int
)
ret
);
return
(
ret
);
}
else
if
(
cmd
==
(
BIO_CB_WRITE
|
BIO_CB_RETURN
))
{
printf
(
"write to %08X [%08lX] (%d bytes => %ld (0x%X))"
,
(
unsigned
int
)
bio
,
(
long
unsigned
int
)
argp
,
argi
,
ret
,
(
unsigned
int
)
ret
);
tls_dump
(
argp
,
(
int
)
ret
);
}
return
(
ret
);
}
/*
* This is the actual startup routine for the connection. We expect
* that the buffers are flushed and the "Ready to start TLS" was
* send to the client, so that we can immediately can start the TLS
* handshake process.
*
* 'layerbits' and 'authid' are filled in on success. authid is only
* filled in if the client authenticated. 'ret' is the SSL connection
* on success.
*/
int
tls_start_servertls
(
int
readfd
,
int
writefd
,
int
*
layerbits
,
char
**
authid
,
SSL
**
ret
)
{
int
sts
;
int
j
;
unsigned
int
n
;
SSL_CIPHER
*
cipher
;
X509
*
peer
;
const
char
*
tls_protocol
=
NULL
;
const
char
*
tls_cipher_name
=
NULL
;
int
tls_cipher_usebits
=
0
;
int
tls_cipher_algbits
=
0
;
SSL
*
tls_conn
;
int
r
=
0
;
assert
(
tls_serverengine
);
assert
(
ret
);
if
(
var_imapd_tls_loglevel
>=
1
)
syslog
(
LOG_DEBUG
,
"setting up TLS connection"
);
if
(
authid
)
*
authid
=
NULL
;
tls_conn
=
(
SSL
*
)
SSL_new
(
s_ctx
);
if
(
tls_conn
==
NULL
)
{
*
ret
=
NULL
;
r
=
-1
;
goto
done
;
}
SSL_clear
(
tls_conn
);
/* set the file descriptors for SSL to use */
if
((
SSL_set_rfd
(
tls_conn
,
readfd
)
==
0
)
||
(
SSL_set_wfd
(
tls_conn
,
writefd
)
==
0
))
{
r
=
-1
;
goto
done
;
}
/*
* This is the actual handshake routine. It will do all the negotiations
* and will check the client cert etc.
*/
SSL_set_accept_state
(
tls_conn
);
/*
* We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
* Well there is a BIO below the SSL routines that is automatically
* created for us, so we can use it for debugging purposes.
*/
if
(
var_imapd_tls_loglevel
>=
3
)
BIO_set_callback
(
SSL_get_rbio
(
tls_conn
),
bio_dump_cb
);
/* Dump the negotiation for loglevels 3 and 4*/
if
(
var_imapd_tls_loglevel
>=
3
)
do_dump
=
1
;
if
((
sts
=
SSL_accept
(
tls_conn
))
<=
0
)
{
SSL_SESSION
*
session
=
SSL_get_session
(
tls_conn
);
if
(
session
)
{
SSL_CTX_remove_session
(
s_ctx
,
session
);
}
r
=
-1
;
goto
done
;
}
/* Only loglevel==4 dumps everything */
if
(
var_imapd_tls_loglevel
<
4
)
do_dump
=
0
;
/*
* Lets see, whether a peer certificate is available and what is
* the actual information. We want to save it for later use.
*/
peer
=
SSL_get_peer_certificate
(
tls_conn
);
if
(
peer
!=
NULL
)
{
char
fingerprint
[
EVP_MAX_MD_SIZE
*
3
];
char
issuer_CN
[
CCERT_BUFSIZ
];
char
peer_issuer
[
CCERT_BUFSIZ
];
char
peer_CN
[
CCERT_BUFSIZ
];
char
peer_subject
[
CCERT_BUFSIZ
];
unsigned
char
md
[
EVP_MAX_MD_SIZE
];
syslog
(
LOG_DEBUG
,
"received client certificate"
);
X509_NAME_oneline
(
X509_get_subject_name
(
peer
),
peer_subject
,
CCERT_BUFSIZ
);
syslog
(
LOG_DEBUG
,
"subject=%s"
,
peer_subject
);
X509_NAME_oneline
(
X509_get_issuer_name
(
peer
),
peer_issuer
,
CCERT_BUFSIZ
);
if
(
var_imapd_tls_loglevel
>=
2
)
syslog
(
LOG_DEBUG
,
"issuer=%s"
,
peer_issuer
);
if
(
X509_digest
(
peer
,
EVP_md5
(),
md
,
&
n
))
{
for
(
j
=
0
;
j
<
(
int
)
n
;
j
++
)
{
fingerprint
[
j
*
3
]
=
hexcodes
[(
md
[
j
]
&
0xf0
)
>>
4
];
fingerprint
[(
j
*
3
)
+
1
]
=
hexcodes
[(
md
[
j
]
&
0x0f
)];
if
(
j
+
1
!=
(
int
)
n
)
{
fingerprint
[(
j
*
3
)
+
2
]
=
'_'
;
}
else
{
fingerprint
[(
j
*
3
)
+
2
]
=
'\0'
;
}
}
if
(
var_imapd_tls_loglevel
>=
2
)
syslog
(
LOG_DEBUG
,
"fingerprint=%s"
,
fingerprint
);
}
X509_NAME_get_text_by_NID
(
X509_get_subject_name
(
peer
),
NID_commonName
,
peer_CN
,
CCERT_BUFSIZ
);
X509_NAME_get_text_by_NID
(
X509_get_issuer_name
(
peer
),
NID_commonName
,
issuer_CN
,
CCERT_BUFSIZ
);
if
(
var_imapd_tls_loglevel
>=
3
)
syslog
(
LOG_DEBUG
,
"subject_CN=%s, issuer_CN=%s"
,
peer_CN
,
issuer_CN
);
/* xxx verify that we like the peer_issuer/issuer_CN */
if
(
authid
!=
NULL
)
{
/* save the peer id for our caller */
*
authid
=
peer_CN
?
xstrdup
(
peer_CN
)
:
NULL
;
}
X509_free
(
peer
);
}
tls_protocol
=
SSL_get_version
(
tls_conn
);
cipher
=
SSL_get_current_cipher
(
tls_conn
);
tls_cipher_name
=
SSL_CIPHER_get_name
(
cipher
);
tls_cipher_usebits
=
SSL_CIPHER_get_bits
(
cipher
,
&
tls_cipher_algbits
);
if
(
layerbits
!=
NULL
)
{
*
layerbits
=
tls_cipher_usebits
;
}
if
(
authid
&&
*
authid
)
{
syslog
(
LOG_NOTICE
,
"starttls: %s with cipher %s (%d/%d bits %s)"
" authenticated as %s"
,
tls_protocol
,
tls_cipher_name
,
tls_cipher_usebits
,
tls_cipher_algbits
,
SSL_session_reused
(
tls_conn
)
?
"reused"
:
"new"
,
*
authid
);
}
else
{
syslog
(
LOG_NOTICE
,
"starttls: %s with cipher %s (%d/%d bits %s)"
" no authentication"
,
tls_protocol
,
tls_cipher_name
,
tls_cipher_usebits
,
tls_cipher_algbits
,
SSL_session_reused
(
tls_conn
)
?
"reused"
:
"new"
);
}
done
:
if
(
r
&&
tls_conn
)
{
/* error; clean up */
SSL_free
(
tls_conn
);
tls_conn
=
NULL
;
}
*
ret
=
tls_conn
;
return
r
;
}
int
tls_reset_servertls
(
SSL
**
conn
)
{
int
r
=
0
;
if
(
*
conn
)
{
if
(
TLS_FAST_SHUTDOWN
)
{
/*
* Don't bother spending time closing the TLS session,
* but make sure its available for reuse.
*/
SSL_set_shutdown
(
*
conn
,
SSL_SENT_SHUTDOWN
|
SSL_RECEIVED_SHUTDOWN
);
}
else
{
/* Follow the TLS protocol and do a shutdown handshake */
r
=
SSL_shutdown
(
*
conn
);
if
(
r
==
0
)
{
/* Just sent, now wait for peer to ack */
r
=
SSL_shutdown
(
*
conn
);
}
if
(
r
==
0
)
r
=
-1
;
}
SSL_free
(
*
conn
);
}
return
r
;
}
int
tls_shutdown_serverengine
(
void
)
{
int
r
;
if
(
tls_serverengine
&&
sess_dbopen
)
{
r
=
DB
->
close
(
sessdb
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"DBERROR: error closing tlsdb: %s"
,
cyrusdb_strerror
(
r
));
}
sessdb
=
NULL
;
sess_dbopen
=
0
;
}
return
0
;
}
/*
* Delete expired sessions.
*/
struct
prunerock
{
int
count
;
int
deletions
;
};
static
int
prune_p
(
void
*
rock
,
const
char
*
id
,
int
idlen
,
const
char
*
data
,
int
datalen
)
{
struct
prunerock
*
prock
=
(
struct
prunerock
*
)
rock
;
time_t
expire
;
prock
->
count
++
;
assert
(
datalen
>=
(
int
)
sizeof
(
time_t
));
/* grab the expire time */
memcpy
(
&
expire
,
data
,
sizeof
(
time_t
));
/* log this transaction */
if
(
var_imapd_tls_loglevel
>
0
)
{
int
i
;
char
idstr
[
SSL_MAX_SSL_SESSION_ID_LENGTH
*
2
+
1
];
assert
(
idlen
<=
SSL_MAX_SSL_SESSION_ID_LENGTH
);
for
(
i
=
0
;
i
<
idlen
;
i
++
)
{
sprintf
(
idstr
+
i
*
2
,
"%02X"
,
(
unsigned
char
)
id
[
i
]);
}
syslog
(
LOG_DEBUG
,
"found TLS session: id=%s, expire=%s"
,
idstr
,
ctime
(
&
expire
));
}
/* check if the session has expired */
return
(
expire
<
time
(
0
));
}
static
int
prune_cb
(
void
*
rock
,
const
char
*
id
,
int
idlen
,
const
char
*
data
__attribute__
((
unused
)),
int
datalen
__attribute__
((
unused
)))
{
struct
prunerock
*
prock
=
(
struct
prunerock
*
)
rock
;
prock
->
deletions
++
;
remove_session
((
unsigned
char
*
)
id
,
idlen
);
return
0
;
}
/* must be called after cyrus_init */
int
tls_prune_sessions
(
void
)
{
char
dbdir
[
1024
];
int
ret
;
struct
prunerock
prock
;
/* create the name of the db file */
strlcpy
(
dbdir
,
config_dir
,
sizeof
(
dbdir
));
strlcat
(
dbdir
,
FNAME_TLSSESSIONS
,
sizeof
(
dbdir
));
ret
=
DB
->
open
(
dbdir
,
CYRUSDB_CREATE
,
&
sessdb
);
if
(
ret
!=
CYRUSDB_OK
)
{
syslog
(
LOG_ERR
,
"DBERROR: opening %s: %s"
,
dbdir
,
cyrusdb_strerror
(
ret
));
return
1
;
}
else
{
/* check each session in our database */
sess_dbopen
=
1
;
prock
.
count
=
prock
.
deletions
=
0
;
DB
->
foreach
(
sessdb
,
""
,
0
,
&
prune_p
,
&
prune_cb
,
&
prock
,
NULL
);
DB
->
close
(
sessdb
);
sessdb
=
NULL
;
sess_dbopen
=
0
;
syslog
(
LOG_NOTICE
,
"tls_prune: purged %d out of %d entries"
,
prock
.
deletions
,
prock
.
count
);
}
return
0
;
}
/* fill string buffer with info about tls connection */
int
tls_get_info
(
SSL
*
conn
,
char
*
buf
,
size_t
len
)
{
int
usebits
=
0
;
int
algbits
=
0
;
usebits
=
SSL_get_cipher_bits
(
conn
,
&
algbits
);
snprintf
(
buf
,
len
,
"version=%s cipher=%s bits=%d/%d verify=%s"
,
SSL_get_cipher_version
(
conn
),
SSL_get_cipher_name
(
conn
),
usebits
,
algbits
,
SSL_get_verify_result
(
conn
)
==
X509_V_OK
?
"YES"
:
"NO"
);
return
(
strlen
(
buf
));
}
int
tls_init_clientengine
(
int
verifydepth
,
char
*
var_tls_cert_file
,
char
*
var_tls_key_file
)
{
int
off
=
0
;
int
verify_flags
=
SSL_VERIFY_NONE
;
const
char
*
CApath
;
const
char
*
CAfile
;
char
*
c_cert_file
;
char
*
c_key_file
;
if
(
tls_clientengine
)
return
(
0
);
/* already running */
if
(
var_proxy_tls_loglevel
>=
2
)
syslog
(
LOG_DEBUG
,
"starting TLS client engine"
);
SSL_library_init
();
SSL_load_error_strings
();
if
(
tls_rand_init
()
==
-1
)
{
printf
(
"TLS client engine: cannot seed PRNG
\n
"
);
return
-1
;
}
c_ctx
=
SSL_CTX_new
(
TLSv1_client_method
());
if
(
c_ctx
==
NULL
)
{
return
(
-1
);
};
off
|=
SSL_OP_ALL
;
/* Work around all known bugs */
SSL_CTX_set_options
(
c_ctx
,
off
);
SSL_CTX_set_info_callback
(
c_ctx
,
(
void
(
*
)())
apps_ssl_info_callback
);
CAfile
=
config_getstring
(
IMAPOPT_TLS_CA_FILE
);
CApath
=
config_getstring
(
IMAPOPT_TLS_CA_PATH
);
if
((
!
SSL_CTX_load_verify_locations
(
c_ctx
,
CAfile
,
CApath
))
||
(
!
SSL_CTX_set_default_verify_paths
(
c_ctx
)))
{
/* just a warning since this is only necessary for client auth */
syslog
(
LOG_NOTICE
,
"TLS client engine: cannot load CA data"
);
}
if
(
strlen
(
var_tls_cert_file
)
==
0
)
c_cert_file
=
NULL
;
else
c_cert_file
=
var_tls_cert_file
;
if
(
strlen
(
var_tls_key_file
)
==
0
)
c_key_file
=
NULL
;
else
c_key_file
=
var_tls_key_file
;
if
(
c_cert_file
||
c_key_file
)
{
if
(
!
set_cert_stuff
(
c_ctx
,
c_cert_file
,
c_key_file
))
{
syslog
(
LOG_ERR
,
"TLS client engine: cannot load cert/key data"
);
return
(
-1
);
}
}
SSL_CTX_set_tmp_rsa_callback
(
c_ctx
,
tmp_rsa_cb
);
verify_depth
=
verifydepth
;
SSL_CTX_set_verify
(
c_ctx
,
verify_flags
,
verify_callback
);
tls_clientengine
=
1
;
return
(
0
);
}
int
tls_start_clienttls
(
int
readfd
,
int
writefd
,
int
*
layerbits
,
char
**
authid
,
SSL
**
ret
,
SSL_SESSION
**
sess
)
{
int
sts
;
SSL_CIPHER
*
cipher
;
X509
*
peer
;
const
char
*
tls_protocol
=
NULL
;
const
char
*
tls_cipher_name
=
NULL
;
int
tls_cipher_usebits
=
0
;
int
tls_cipher_algbits
=
0
;
SSL
*
tls_conn
;
int
r
=
0
;
assert
(
tls_clientengine
);
assert
(
ret
);
if
(
var_proxy_tls_loglevel
>=
1
)
syslog
(
LOG_DEBUG
,
"setting up TLS connection"
);
if
(
authid
)
*
authid
=
NULL
;
tls_conn
=
(
SSL
*
)
SSL_new
(
c_ctx
);
if
(
tls_conn
==
NULL
)
{
*
ret
=
NULL
;
r
=
-1
;
goto
done
;
}
SSL_clear
(
tls_conn
);
/* set the file descriptors for SSL to use */
if
((
SSL_set_rfd
(
tls_conn
,
readfd
)
==
0
)
||
(
SSL_set_wfd
(
tls_conn
,
writefd
)
==
0
))
{
r
=
-1
;
goto
done
;
}
/*
* This is the actual handshake routine. It will do all the negotiations
* and will check the client cert etc.
*/
SSL_set_connect_state
(
tls_conn
);
/*
* We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
* Well there is a BIO below the SSL routines that is automatically
* created for us, so we can use it for debugging purposes.
*/
if
(
var_proxy_tls_loglevel
>=
3
)
BIO_set_callback
(
SSL_get_rbio
(
tls_conn
),
bio_dump_cb
);
/* Dump the negotiation for loglevels 3 and 4*/
if
(
var_proxy_tls_loglevel
>=
3
)
do_dump
=
1
;
if
(
sess
&&
*
sess
)
/* Reuse a session if we have one */
SSL_set_session
(
tls_conn
,
*
sess
);
if
((
sts
=
SSL_connect
(
tls_conn
))
<=
0
)
{
SSL_SESSION
*
session
=
SSL_get_session
(
tls_conn
);
if
(
session
)
{
SSL_CTX_remove_session
(
c_ctx
,
session
);
}
if
(
sess
)
*
sess
=
NULL
;
r
=
-1
;
goto
done
;
}
if
(
sess
)
*
sess
=
SSL_get_session
(
tls_conn
);
/* Only loglevel==4 dumps everything */
if
(
var_proxy_tls_loglevel
<
4
)
do_dump
=
0
;
/*
* Lets see, whether a peer certificate is available and what is
* the actual information. We want to save it for later use.
*/
peer
=
SSL_get_peer_certificate
(
tls_conn
);
if
(
peer
!=
NULL
)
{
char
issuer_CN
[
CCERT_BUFSIZ
];
char
peer_CN
[
CCERT_BUFSIZ
];
syslog
(
LOG_DEBUG
,
"received server certificate"
);
X509_NAME_get_text_by_NID
(
X509_get_subject_name
(
peer
),
NID_commonName
,
peer_CN
,
CCERT_BUFSIZ
);
X509_NAME_get_text_by_NID
(
X509_get_issuer_name
(
peer
),
NID_commonName
,
issuer_CN
,
CCERT_BUFSIZ
);
if
(
var_proxy_tls_loglevel
>=
3
)
syslog
(
LOG_DEBUG
,
"subject_CN=%s, issuer_CN=%s"
,
peer_CN
,
issuer_CN
);
/* xxx verify that we like the peer_issuer/issuer_CN */
if
(
authid
!=
NULL
)
{
/* save the peer id for our caller */
*
authid
=
peer_CN
?
xstrdup
(
peer_CN
)
:
NULL
;
}
X509_free
(
peer
);
}
tls_protocol
=
SSL_get_version
(
tls_conn
);
cipher
=
SSL_get_current_cipher
(
tls_conn
);
tls_cipher_name
=
SSL_CIPHER_get_name
(
cipher
);
tls_cipher_usebits
=
SSL_CIPHER_get_bits
(
cipher
,
&
tls_cipher_algbits
);
if
(
layerbits
!=
NULL
)
*
layerbits
=
tls_cipher_usebits
;
syslog
(
LOG_NOTICE
,
"starttls: %s with cipher %s (%d/%d bits %s)"
" no authentication"
,
tls_protocol
,
tls_cipher_name
,
tls_cipher_usebits
,
tls_cipher_algbits
,
SSL_session_reused
(
tls_conn
)
?
"reused"
:
"new"
);
done
:
if
(
r
&&
tls_conn
)
{
/* error; clean up */
SSL_free
(
tls_conn
);
tls_conn
=
NULL
;
}
*
ret
=
tls_conn
;
return
r
;
}
#else
int
tls_enabled
(
void
)
{
return
0
;
}
#endif
/* HAVE_SSL */
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Apr 4, 2:08 AM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18822086
Default Alt Text
tls.c (33 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline