bug-cvs
[Top][All Lists]
Advanced

[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 */



reply via email to

[Prev in Thread] Current Thread [Next in Thread]