[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
TLS/SSL patch (alpha)
From: |
Bear Giles |
Subject: |
TLS/SSL patch (alpha) |
Date: |
Mon, 11 Jun 2001 02:16:46 -0600 (MDT) |
Attached is "tls_buffer" code that adds TLS/SSL support to CVS.
It is *not* ready to integration, it might not even compile,
but it's enough to start asking questions.
Also, following up on my earlier comments the first step on
pulling out the authentication code seems to be pulling out
all "buffer" routines. Perhaps into a subdirectory, with a
library created which is used by the rest of the system. This
library would include the krb4 and gssapi buffers.
Bear Giles
bgiles (at) coyotesong (dot) com
/* TLS (OpenSSL) buffer stuff.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
/* These routines implement a buffer structure which uses OpenSSL
to provide TLS/SSL encryption of the data stream. It is closely
related to the "socket" buffer. */
/* Note that it is important that these routines always handle errors
internally and never return a positive errno code, since it would in
general be impossible for the caller to know in general whether any
error code came from an OpenSSL routine (to decide whether to use
(???) or simply strerror to print an error message). */
/* ALPHA - ALPHA - ALPHA - ALPHA - ALPHA - ALPHA - ALPHA - ALPHA
This code has *no* guarantees, not even that it will compile.
However, it is sufficient to ask intelligent questions about
TLS/SSL integration into CVS.
1) the cleanest way to integrate TLS/SSL into CVS is to
use an "upgrade" mechanism - early in negotiations the
client can request
Starttls
and this buffer will be pushed onto the existing
socket/fd buffer.
However, two buffers are defined for the socket and fd
buffers, but we must have a combined view because of
the traffic which occurs at the TLS/SSL layer but not
the application layer.
Is there a clean way to do this? Or will this require
revisiting the design of the socket and fd buffer
initialization?
2) Should the server require a digital cert? (easy answer
is 'yes' - it can always use a self-signed cert generated
with OpenSSL.)
Should the client require a digital cert? Without a
cert, TLS/SSL can only be used for encryption. With a
cert, it can be used for authentication as well.
3) Alternately, instead of checking the certificate chain
maybe it makes more sense to use a strategy like SSH's
"known_hosts" and "authorized_keys" schema. The "known_hosts"
would be CVS servers known to the client, and the "authorized_keys"
would be user keys recognized by the server.
4) SMTP experience shows that it is best to include
a FQDN with the 'starttls' command if server certs are
required. Without one, you can't implement virtual
domains.
So
Starttls
becomes
Starttls root
and server certs management becomes that much more
complex. However, if 'root' isn't specified we can fall
back to a common cert.
5) support for session resumption? Since the client is usually
run as a series of separate processes, I don't know if this
is practical.
6) TLS/SSL uses packets, not byte-wise transmission, and it's
possible this holds surprises for us....
7) For the truly paranoid, should we support automatic SSL key
renegotiation?
8) Integration with SASL.
*/
/* This code borrows heavily from "SSL and TLS" by Eric Rescorla */
#define CA_LIST "root.pem"
/* The number of bits to be used for each session. This is
independent of the number of bits in the digital certs.
This should normally be 512 - 1024; for a long time US
export restrictions had a de facto limit of 40 bits. */
#define SESSKEYSIZE 512
/* We set the authenticated name of the user to match the
digital certificate's DN */
extern char *CVS_Username;
/* Location of digital certificate for server or user, with
optional password. The server keyfile will probably be
located someplace like "/etc/cvs/cvs.pem", the user cert
will probably be something like "/home/user/.cvs/user.pem"
'keyfile' and 'keyfile_private' are kept separate so it
will be easy to migrate to a schema similar to SSH's
"known_hosts" and "authorized_keys", instead of using
checking the certificate chain. */
char *cvs_server_keyfile;
char *cvs_server_keyfile_private;
char *cvs_user_keyfile;
char *cvs_user_keyfile_private;
char *cvs_keyfile_password;
/* Location of DH file. This should be NULL for a client and
a file pathname for a server. (In fact, this is how we
identify servers! It should be something like /etc/cvs/dh1024.pem */
char *cvs_dhfile;
/* flag that indicates if we're server or client - or is there
a cleaner way to determine this? */
extern int i_am_server;
/* We use an instance of this structure as the closure field. */
BIO *bio_err = NULL;
struct tls_buffer
{
/* The socket number. */
int socket;
SSL_CTX *ctx;
SSL *ssl;
BIO *sbio;
};
struct buffer *tls_buffer_initialize
PROTO ((int, int, void (*) (struct buffer *)));
static int tls_buffer_input PROTO((void *, char *, int, int, int *));
static int tls_buffer_output PROTO((void *, const char *, int, int *));
static int tls_buffer_flush PROTO((void *));
static SSL_CTX *initialize_ctx PROTO((void));
static void generate_eph_rsa_key PROTO((SSL_CTX *ctx));
static void load_dh_params PROTO((SSL_CTX *ctx, const char *file));
static void check_cert PROTO((SSL *ssl));
/* Create a buffer based on TLS/SSL. */
/* N.B., unlike most of the initialization routines this one
must create both 'input' and 'output' */
struct buffer *
tls_buffer_initialize (socket, input, memory)
int socket;
// int input;
void (*memory) PROTO((struct buffer *))
{
struct tls_buffer *tb;
tb = (struct tls_buffer *) xmalloc (sizeof *tb);
tb->ctx = initialize_ctx ();
/* negotiate a SSL connection */
tb->socket = socket;
tb->ssl = SSL_new (ctx);
tb->sbio = BIO_new_socket(socket, BIO_NOCLOSE);
SSL_set_bio (ssl, sbio, sbio);
if (SSL_connect (ssl) <= 0) {
error (1, 0, "connecting to TLS");
}
check_cert (ssl);
return buf_initialize (tls_buffer_input,
tls_buffer_output,
tls_buffer_flush,
(int (*) PROTO((void *, int))) NULL,
tls_buffer_shutdown,
memory,
n);
}
/* The buffer input function for a buffer built on a socket. */
static int
tls_buffer_input (closure, data, need, size, got)
void *closure;
char *data;
int need;
int size;
int *got;
{
struct tls_buffer *tb = (struct tls_buffer *) closure;
int nbytes;
*got = 0;
do
{
nbytes = SSL_read (tb->ssl, data, need);
switch (SSL_get_error(ssl, nbytes))
{
case SSL_ERROR_NONE:
need -= nbytes;
size -= nbytes;
data += nbytes;
got += nbytes;
break;
/* End of file (for example, the server has closed
the connection). If we've already read something, we
just tell the caller about the data, not about the end of
file. If we've read nothing, we return end of file. */
case SSL_ERROR_ZERO_READ;
if (*got == 0)
return -1;
else
return 0;
default:
error (1, 0, "writing reading from TLS");
}
}
while (need > 0);
return 0;
}
/* The buffer output function for a buffer built on a socket. */
static int
tls_buffer_output (closure, data, have, wrote)
void *closure;
const char *data;
int have;
int *wrote;
{
struct tls_buffer *tb = (struct tls_buffer *) closure;
*wrote = have;
while (have > 0)
{
int nbytes;
nbytes = SSL_write (tb->ssl, data, have);
switch (SSL_get_error(ssl, r)) {
case SSL_ERROR_NONE:
have -= nbytes;
data += nbytes;
break;
default:
error (1, 0, "writing to TLS");
}
}
return 0;
}
/* The buffer flush function for a buffer built on a OpenSSL. */
/*ARGSUSED*/
static int
tls_buffer_flush (closure)
void *closure;
{
/* Nothing to do. Sockets are always flushed. */
return 0;
}
/* The buffer shutdown function for a buffer built on OpenSSL. */
/*ARGSUSED*/
static int
tls_buffer_shutdown (closure)
void *closure;
{
struct tls_buffer *tb = (struct tls_buffer *) closure;
SSL_shutdown (tb->ssl);
SSL_free (tb->ssl);
tb->ssl = NULL;
#if 0
close (tb->sock);
tb->sock = 0;
#endif
SSL_CTX_free (tb->ctx);
tb->ctx = NULL;
return 0;
}
/* The password code is not thread safe */
static char *pass;
static int password_cb (char *buf, int num, int rwflag, void *userdata)
{
if (num < strlen(pass)+1)
return 0;
strcpy (buf, pass);
return strlen(pass);
}
/* Load Duffie-Huffman parameters */
static void load_dh_params (SSL_CTX *ctx, const char *file)
{
DH *ret = 0;
BIO *bio;
if ((bio = BIO_new_file (file, "r")) == NULL)
error (1, 0, "Can't open DH file");
ret = PEM_read_bio_DHparams (bio, NULL, NULL, NULL);
BIO_free (bio);
if (SSL_CTX_set_tmp_dh (ctx, ret) < 0)
error (1, 0, "Can't set DH parameters");
}
/* Generate new key */
static void generate_eph_rsa_key (SSL_CTX *ctx)
{
RSA *rsa;
rsa = RSA_generate_key (SESSKEYSIZE, rSA_F4, NULL, NULL);
if (!SSL_CTX_set_tmp_rsa (ctx, rsa))
error (1, 0, "can't set RSA key");
RSA_free (rsa);
}
/* Check the cert received from the other side. In both
cases, we want to check the cert chain first. If we're
operating our own organizational CA, this is both easy
and critical. (Unless nobody ever leaves, let alone
leaves angry!)
If we are the client, we want to make sure that the
cert's comman name matches the hostname specified when
we called "connect" on the socket. This is necessary,
but not sufficient, to prevent a man-in-the-middle
attack.
If we are the server, we use the cert's "common name"
as the authenticated user name.
/* Check that the common name matches what's expected *
static void check_cert (SSL *ssl)
{
X509 *peer;
char peer_CN[256];
if (SSL_get_verify_result(ssl) != X509_V_OK)
error (1, 0, "Certificate doesn't verify");
/* Check the cert chain. The chain length is automatically
checked by OpenSSL when we set the verify depth in the CTX.
All we need to do here is check that the CN matches. */
peer = SSL_get_peer_certificate(ssl);
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, peer_CN, 256);
if (i_am_server) {
#if 0
/* how do we get 'host' from upstream? */
/* what about getsockname()? */
if (strcasecmp (peer_CN, host))
error (1, 0, "Common name doesn't match host name");
#endif
}
else {
CVS_Username = strdup (peer_CN):
}
}
/* Initialize the CTX context - keeps tls_buffer_initialize() clean */
static SSL_CTX *initialize_ctx ()
{
SSL_METHOD *meth;
SSL_CTX *ctx;
const char *keyfile;
if (!bio_err) {
/* global system initialization */
SSL_library_init();
SSL_load_error_strings();
/* An error write context */
/* FIXME - do we need this contex, or just a binary flag? */
bio_err = BIO_new_fp (stderr, BIO_NOCLOSE);
}
/* signal (SIGPIPE, sigpipe_handle); */
/* Create our context */
meth = TLSv1_method();
ctx = SSL_CTX_new (meth);
/* load our keys and certificates */
/* we should also check file permissions, etc. */
pass = password;
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
if (i_am_server) {
keyfile = cvs_server_keyfile;
if (!(SSL_CTX_use_certificate_file (ctx, keyfile, SSL_FILETYPE_PEM)))
error (1, 0, "can't read server certificate file");
keyfile = cvs_server_keyfile_private;
if (keyfile == NULL)
keyfile = cvs_server_keyfile;
if (!(SSL_CTX_use_PrivateKey_file (ctx, keyfile, SSL_FILETYPE_PEM)))
error (1, 0, "can't read private server key");
}
else if (cvs_user_keyfile != NULL) {
keyfile = cvs_user_keyfile,
if (!(SSL_CTX_use_certificate_file (ctx, SSL_FILETYPE_PEM)))
error (1, 0, "can't read user certificate file");
keyfile = cvs_user_keyfile_private,
if (keyfile == NULL)
keyfile = cvs_client_keyfile;
if (!(SSL_CTX_use_PrivateKey_file (ctx, keyfile, SSL_FILETYPE_PEM)))
error (1, 0, "can't read private user key");
}
/* Load the CAs we trust */
if (!(SSL_CTX_load_verify_locations (ctx, CA_LIST, 0)))
error (1, 0, "can't read CA list");
SSL_CTX_set_verify_depth (ctx, 1);
/* Load randomness */
if (!(RAND_load_file (RANDOM, 1024*1024)))
error (1, 0, "can't load randomness");
/* if this is the server side of the connection, we
need to load some additional information */
if (cvs_dhfile) {
load_dh_params(ctx, cvs_dhfile);
generate_eph_rsa_key(ctx);
SSL_CTX_set_session_id_context (ctx,
(void *) &s_server_session_id_context,
sizeof s_server_session_id_context);
}
return ctx;
}
#endif /* NO_SOCKET_TO_FD */
- TLS/SSL patch (alpha),
Bear Giles <=
Re: TLS/SSL patch (alpha), Richard Levitte - VMS Whacker, 2001/06/14