Additional EAP-TLS Logging Option

Ross, Michael michael.ross2 at boeing.com
Tue Jul 27 16:55:26 CEST 2010


Hello,

I customized a freeradius installation to include an option to log information from the client certificates (subject, issuer, serial, expiration) when doing eap-tls authentication.  This is done through a new option, log_certificates, in the tls section of the eap.conf file.  Is there any interest in adding this capability into the baseline freeradius?  I'm happy to make any changes that would be required to do that.  Below are the updated rlm_eap_tls.h and rlm_eap_tls.c.

Thanks,
Mike Ross

/*
 * rlm_eap_tls.h
 *
 * Version:     $Id: rlm_eap_tls.h,v 1.13 2007/04/25 14:19:26 aland Exp $
 *
 *   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 of the License, 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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2001  hereUare Communications, Inc. <raghud at hereuare.com>
 * Copyright 2003  Alan DeKok <aland at freeradius.org>
 * Copyright 2006  The FreeRADIUS server project
 *
 *-----------------------------------------------------------------------------
 * Modified by The Boeing Company, August 26, 2008
 *
 * Added int log_certificates to the eap_tls_conf struct
 *
 * Copyright 2008  The Boeing Company <Michael.Ross2 at boeing.com>
 *-----------------------------------------------------------------------------
 */
#ifndef _RLM_EAP_TLS_H
#define _RLM_EAP_TLS_H

#include <freeradius-devel/ident.h>
RCSIDH(rlm_eap_tls_h, "$Id: rlm_eap_tls.h,v 1.13 2007/04/25 14:19:26 aland Exp $")

#include "eap_tls.h"

#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>

/* configured values goes right here */
typedef struct eap_tls_conf {
        char            *private_key_password;
        char            *private_key_file;
        char            *certificate_file;
        char            *random_file;
        char            *ca_path;
        char            *ca_file;
        char            *dh_file;
        char            *rsa_file;
        char            *make_cert_command;
        int             rsa_key;
        int             dh_key;
        int             rsa_key_length;
        int             dh_key_length;
        int             verify_depth;
        int             file_type;
        int             include_length;

        /*
         *      Always < 4096 (due to radius limit), 0 by default = 2048
         */
        int             fragment_size;
        int             check_crl;
        char            *check_cert_cn;
        char            *cipher_list;
        char            *check_cert_issuer;
        int             log_certificates;       /* log_certificates added by Boeing, 9/26/2008 */
} EAP_TLS_CONF;

/* This structure gets stored in arg */
typedef struct _eap_tls_t {
        EAP_TLS_CONF    *conf;
        SSL_CTX         *ctx;
} eap_tls_t;


#endif /* _RLM_EAP_TLS_H */

/*
 * rlm_eap_tls.c  contains the interfaces that are called from eap
 *
 * Version:     $Id: rlm_eap_tls.c,v 1.54 2008/03/22 08:31:03 aland Exp $
 *
 *   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 of the License, 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.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2001  hereUare Communications, Inc. <raghud at hereuare.com>
 * Copyright 2003  Alan DeKok <aland at freeradius.org>
 * Copyright 2006  The FreeRADIUS server project
 *
 *-----------------------------------------------------------------------------
 * Modified by The Boeing Company, August 26, 2008
 *
 * Added log_certificates to module_config CONF_PARSER
 * Added additional authentication logging into cbtls_verify
 * Added log_full_cert function to write certificate into log file
 *
 * Copyright 2008  The Boeing Company <Michael.Ross2 at boeing.com>
 *-----------------------------------------------------------------------------
 */

#include <freeradius-devel/ident.h>
RCSID("$Id: rlm_eap_tls.c,v 1.54 2008/03/22 08:31:03 aland Exp $")

#include <freeradius-devel/autoconf.h>

#ifdef HAVE_OPENSSL_RAND_H
#include <openssl/rand.h>
#endif

#include "rlm_eap_tls.h"

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

static CONF_PARSER module_config[] = {
        { "rsa_key_exchange", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, rsa_key), NULL, "no" },
        { "dh_key_exchange", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, dh_key), NULL, "yes" },
        { "rsa_key_length", PW_TYPE_INTEGER,
          offsetof(EAP_TLS_CONF, rsa_key_length), NULL, "512" },
        { "dh_key_length", PW_TYPE_INTEGER,
          offsetof(EAP_TLS_CONF, dh_key_length), NULL, "512" },
        { "verify_depth", PW_TYPE_INTEGER,
          offsetof(EAP_TLS_CONF, verify_depth), NULL, "0" },
        { "CA_path", PW_TYPE_FILENAME,
          offsetof(EAP_TLS_CONF, ca_path), NULL, NULL },
        { "pem_file_type", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, file_type), NULL, "yes" },
        { "private_key_file", PW_TYPE_FILENAME,
          offsetof(EAP_TLS_CONF, private_key_file), NULL, NULL },
        { "certificate_file", PW_TYPE_FILENAME,
          offsetof(EAP_TLS_CONF, certificate_file), NULL, NULL },
        { "CA_file", PW_TYPE_FILENAME,
          offsetof(EAP_TLS_CONF, ca_file), NULL, NULL },
        { "private_key_password", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, private_key_password), NULL, NULL },
        { "dh_file", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, dh_file), NULL, NULL },
        { "random_file", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, random_file), NULL, NULL },
        { "fragment_size", PW_TYPE_INTEGER,
          offsetof(EAP_TLS_CONF, fragment_size), NULL, "1024" },
        { "include_length", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, include_length), NULL, "yes" },
        { "check_crl", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, check_crl), NULL, "no"},
        { "check_cert_cn", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, check_cert_cn), NULL, NULL},
        { "cipher_list", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, cipher_list), NULL, NULL},
        { "check_cert_issuer", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, check_cert_issuer), NULL, NULL},
        { "make_cert_command", PW_TYPE_STRING_PTR,
          offsetof(EAP_TLS_CONF, make_cert_command), NULL, NULL},
        /* log_certificates added by Boeing, 9/26/2008 */
        { "log_certificates", PW_TYPE_BOOLEAN,
          offsetof(EAP_TLS_CONF, log_certificates), NULL, "no" },

        { NULL, -1, 0, NULL, NULL }           /* end the list */
};


/*
 *      log_full_cert added by Boeing, 9/26/2008
 *
 *  Write the certificate serial number, expiration
 *  date, issuer, and subject to the log file
 */
static int log_full_cert(int ok, X509 *cert)
{
        const int ub_serial = 20;
        const int ub_time = 15;

        char serialNumber[2*ub_serial+1];
        char expiration[ub_time+1];
        char issuer[ub_name+1];
        char subject[ub_name+1];
        char *pTemp = NULL;
        int local_ok = ok;
        int i = 0;
        ASN1_INTEGER *pSn = NULL;
        ASN1_TIME *pTime = NULL;

        /*
         *  Get the Serial Number and format for display
         */
        serialNumber[0] = '\0';
        pSn = X509_get_serialNumber(cert);
        pTemp = serialNumber;
        if ( pSn == NULL || pSn->length > ub_serial ) {
                radlog(L_AUTH, "rlm_eap_tls:  Malformed Certificate Serial Number");
                local_ok = 0;
        } else {
                for (i = 0; i < pSn->length; i++) {
                        sprintf(pTemp, "%02x", (int)pSn->data[i]);
                        pTemp += 2;
                }
        }

        /*
         * Get the Expiration Date and format for display
         */
        expiration[0] = '\0';
        pTime = X509_get_notAfter(cert);
        if ( pTime == NULL  || pTime->length > ub_time ) {
                radlog(L_AUTH, "rlm_eap_tls:  Malformed Certificate Expiration");
                local_ok = 0;
        } else {
                strncpy(expiration, pTime->data, pTime->length);
                expiration[pTime->length] = '\0';
        }

        /*
         *      Get the Issuer
         */
        issuer[0] = '\0';
        X509_NAME_oneline(X509_get_issuer_name(cert), issuer,
                          ub_name);
        issuer[0] = ' ';
        issuer[ub_name] = '\0';
        for (i = 0; i < ub_name; i++) {
                if (issuer[i] == '/') {
                        issuer[i] = ',';
                }
        }

        /*
         *      Get the Subject
         */
        subject[0] = '\0';
        X509_NAME_oneline(X509_get_subject_name(cert), subject,
                          ub_name);
        subject[0] = ' ';
        subject[ub_name] = '\0';
        for (i = 0; i < ub_name; i++) {
                if (subject[i] == '/') {
                        subject[i] = ',';
                }
        }

        /*
         *  Log required attributes
         */
        radlog(L_AUTH, "Certificate: Serial=%s; Expiration=%s; Issuer:%s; Subject:%s",
                serialNumber, expiration, issuer, subject);

        return local_ok;
}


/*
 *      TODO: Check for the type of key exchange * like conf->dh_key
 */
static int load_dh_params(SSL_CTX *ctx, char *file)
{
        DH *dh = NULL;
        BIO *bio;

        if ((bio = BIO_new_file(file, "r")) == NULL) {
                radlog(L_ERR, "rlm_eap_tls: Unable to open DH file - %s", file);
                return -1;
        }

        dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
        BIO_free(bio);
        if (!dh) {
                DEBUG2("WARNING: rlm_eap_tls: Unable to set DH parameters.  DH cipher suites may not work!");
                DEBUG2("WARNING: Fix this by running the OpenSSL command listed in eap.conf");
                return 0;
        }

        if (SSL_CTX_set_tmp_dh(ctx, dh) < 0) {
                radlog(L_ERR, "rlm_eap_tls: Unable to set DH parameters");
                DH_free(dh);
                return -1;
        }

        DH_free(dh);
        return 0;
}

/*
 *      Before trusting a certificate, you must make sure that the
 *      certificate is 'valid'. There are several steps that your
 *      application can take in determining if a certificate is
 *      valid. Commonly used steps are:
 *
 *      1.Verifying the certificate's signature, and verifying that
 *      the certificate has been issued by a trusted Certificate
 *      Authority.
 *
 *      2.Verifying that the certificate is valid for the present date
 *      (i.e. it is being presented within its validity dates).
 *
 *      3.Verifying that the certificate has not been revoked by its
 *      issuing Certificate Authority, by checking with respect to a
 *      Certificate Revocation List (CRL).
 *
 *      4.Verifying that the credentials presented by the certificate
 *      fulfill additional requirements specific to the application,
 *      such as with respect to access control lists or with respect
 *      to OCSP (Online Certificate Status Processing).
 *
 *      NOTE: This callback will be called multiple times based on the
 *      depth of the root certificate chain
 */
static int cbtls_verify(int ok, X509_STORE_CTX *ctx)
{
        char subject[1024]; /* Used for the subject name */
        char issuer[1024]; /* Used for the issuer name */
        char common_name[1024];
        char cn_str[1024];
        EAP_HANDLER *handler = NULL;
        X509 *client_cert;
        SSL *ssl;
        int err, depth;
        EAP_TLS_CONF *conf;
        int my_ok = ok;

        client_cert = X509_STORE_CTX_get_current_cert(ctx);
        err = X509_STORE_CTX_get_error(ctx);
        depth = X509_STORE_CTX_get_error_depth(ctx);

        /*
         *  Moved below the conf check by Boeing, 9/26/2008
         */
        /*
         *if (!my_ok) {
         *      radlog(L_ERR,"--> verify error:num=%d:%s\n",err,
         *              X509_verify_cert_error_string(err));
         *      return my_ok;
         *}
         */

        /*
         * Retrieve the pointer to the SSL of the connection currently treated
         * and the application specific data stored into the SSL object.
         */
        ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
        handler = (EAP_HANDLER *)SSL_get_ex_data(ssl, 0);
        conf = (EAP_TLS_CONF *)SSL_get_ex_data(ssl, 1);

        /*
         *  Copied from above by Boeing, 9/26/2008
         */
        if (!my_ok) {
                radlog(L_ERR,"--> verify error:num=%d:%s\n",err,
                        X509_verify_cert_error_string(err));
                /*
                 *  Added by Boeing, 9/26/2008
                 *  If the config file is set to log certificates
                 *  log the certificate if there has been an authentication
                 *  error
                 */
                if (conf->log_certificates) {
                        log_full_cert(my_ok, client_cert);
                }
                return my_ok;
        }

        /*
         *      Get the Subject & Issuer
         */
        subject[0] = issuer[0] = '\0';
        X509_NAME_oneline(X509_get_subject_name(client_cert), subject,
                          sizeof(subject));
        X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), issuer,
                          sizeof(issuer));

        subject[sizeof(subject) - 1] = '\0';
        issuer[sizeof(issuer) - 1] = '\0';

        /*
         *      Get the Common Name
         */
        X509_NAME_get_text_by_NID(X509_get_subject_name(client_cert),
                                  NID_commonName, common_name, sizeof(common_name));
        common_name[sizeof(common_name) - 1] = '\0';

        switch (ctx->error) {

        case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
                radlog(L_ERR, "issuer= %s\n", issuer);
                break;
        case X509_V_ERR_CERT_NOT_YET_VALID:
        case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
                radlog(L_ERR, "notBefore=");
#if 0
                ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert));
#endif
                break;
        case X509_V_ERR_CERT_HAS_EXPIRED:
        case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
                radlog(L_ERR, "notAfter=");
#if 0
                ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert));
#endif
                break;
        }

        /*
         *      If we're at the actual client cert, apply additional
         *      checks.
         */
        if (depth == 0) {
                /*
                 *      If the conf tells us to, check cert issuer
                 *      against the specified value and fail
                 *      verification if they don't match.
                 */
                if (conf->check_cert_issuer &&
                    (strcmp(issuer, conf->check_cert_issuer) != 0)) {
                        radlog(L_AUTH, "rlm_eap_tls: Certificate issuer (%s) does not match specified value (%s)!", issuer, conf->check_cert_issuer);
                        my_ok = 0;
                }

                /*
                 *      If the conf tells us to, check the CN in the
                 *      cert against xlat'ed value, but only if the
                 *      previous checks passed.
                 */
                if (my_ok && conf->check_cert_cn) {
                        if (!radius_xlat(cn_str, sizeof(cn_str), conf->check_cert_cn, handler->request, NULL)) {
                                radlog(L_ERR, "rlm_eap_tls (%s): xlat failed.",
                                       conf->check_cert_cn);
                                /* if this fails, fail the verification */
                                my_ok = 0;
                        } else {
                                DEBUG2("    rlm_eap_tls: checking certificate CN (%s) with xlat'ed value (%s)", common_name, cn_str);
                                if (strcmp(cn_str, common_name) != 0) {
                                        radlog(L_AUTH, "rlm_eap_tls: Certificate CN (%s) does not match specified value (%s)!", common_name, cn_str);
                                        my_ok = 0;
                                }
                        }
                } /* check_cert_cn */

                /*
                 *  Added by Boeing, 9/26/2008
                 *  If the config file is set to log certificates
                 *  log the certificate
                 */
                if (conf->log_certificates) {
                        my_ok = log_full_cert(my_ok, client_cert);
                }

        } /* depth == 0 */

        if (debug_flag > 0) {
                DEBUG2("chain-depth=%d, ", depth);
                DEBUG2("error=%d", err);

                DEBUG2("--> User-Name = %s", handler->identity);
                DEBUG2("--> BUF-Name = %s", common_name);
                DEBUG2("--> subject = %s", subject);
                DEBUG2("--> issuer  = %s", issuer);
                DEBUG2("--> verify return:%d", my_ok);
        }
        return my_ok;
}


/*
 *      Create Global context SSL and use it in every new session
 *
 *      - Load the trusted CAs
 *      - Load the Private key & the certificate
 *      - Set the Context options & Verify options
 */
static SSL_CTX *init_tls_ctx(EAP_TLS_CONF *conf)
{
        SSL_METHOD *meth;
        SSL_CTX *ctx;
        X509_STORE *certstore;
        int verify_mode = SSL_VERIFY_NONE;
        int ctx_options = 0;
        int type;

        /*
         *      Add all the default ciphers and message digests
         *      Create our context.
         */
        SSL_library_init();
        SSL_load_error_strings();

        meth = TLSv1_method();
        ctx = SSL_CTX_new(meth);

        /*
         * Identify the type of certificates that needs to be loaded
         */
        if (conf->file_type) {
                type = SSL_FILETYPE_PEM;
        } else {
                type = SSL_FILETYPE_ASN1;
        }

        /*
         * Set the password to load private key
         */
        if (conf->private_key_password) {
                SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->private_key_password);
                SSL_CTX_set_default_passwd_cb(ctx, cbtls_password);
        }

        /*
         *      Load our keys and certificates
         *
         *      If certificates are of type PEM then we can make use
         *      of cert chain authentication using openssl api call
         *      SSL_CTX_use_certificate_chain_file.  Please see how
         *      the cert chain needs to be given in PEM from
         *      openSSL.org
         */
        if (type == SSL_FILETYPE_PEM) {
                if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) {
                        radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
                        radlog(L_ERR, "rlm_eap_tls: Error reading certificate file %s", conf->certificate_file);
                        return NULL;
                }

        } else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) {
                radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
                radlog(L_ERR, "rlm_eap_tls: Error reading certificate file %s", conf->certificate_file);
                return NULL;
        }

        /* Load the CAs we trust */
        if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) {
                radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
                radlog(L_ERR, "rlm_eap_tls: Error reading Trusted root CA list %s",conf->ca_file );
                return NULL;
        }
        if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file));
        if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) {
                radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
                radlog(L_ERR, "rlm_eap_tls: Error reading private key file %s", conf->private_key_file);
                return NULL;
        }

        /*
         * Check if the loaded private key is the right one
         */
        if (!SSL_CTX_check_private_key(ctx)) {
                radlog(L_ERR, "rlm_eap_tls: Private key does not match the certificate public key");
                return NULL;
        }

        /*
         *      Set ctx_options
         */
        ctx_options |= SSL_OP_NO_SSLv2;
        ctx_options |= SSL_OP_NO_SSLv3;

        /*
         *      SSL_OP_SINGLE_DH_USE must be used in order to prevent
         *      small subgroup attacks and forward secrecy. Always
         *      using
         *
         *      SSL_OP_SINGLE_DH_USE has an impact on the computer
         *      time needed during negotiation, but it is not very
         *      large.
         */
        ctx_options |= SSL_OP_SINGLE_DH_USE;

        /*
         *      SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS to work around issues
         *      in Windows Vista client.
         *      http://www.openssl.org/~bodo/tls-cbc.txt
         *      http://www.nabble.com/(RADIATOR)-Radiator-Version-3.16-released-t2600070.html
         */
        ctx_options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;

        SSL_CTX_set_options(ctx, ctx_options);

        /*
         *      TODO: Set the RSA & DH
         *      SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa);
         *      SSL_CTX_set_tmp_dh_callback(ctx, cbtls_dh);
         */

        /*
         *      set the message callback to identify the type of
         *      message.  For every new session, there can be a
         *      different callback argument.
         *
         *      SSL_CTX_set_msg_callback(ctx, cbtls_msg);
         */

        /* Set Info callback */
        SSL_CTX_set_info_callback(ctx, cbtls_info);

        /*
         *      Check the certificates for revocation.
         */
#ifdef X509_V_FLAG_CRL_CHECK
        if (conf->check_crl) {
          certstore = SSL_CTX_get_cert_store(ctx);
          if (certstore == NULL) {
            radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
            radlog(L_ERR, "rlm_eap_tls: Error reading Certificate Store");
            return NULL;
          }
          X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
        }
#endif

        /*
         *      Set verify modes
         *      Always verify the peer certificate
         */
        verify_mode |= SSL_VERIFY_PEER;
        verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
        verify_mode |= SSL_VERIFY_CLIENT_ONCE;
        SSL_CTX_set_verify(ctx, verify_mode, cbtls_verify);

        if (conf->verify_depth) {
                SSL_CTX_set_verify_depth(ctx, conf->verify_depth);
        }

        /* Load randomness */
        if (!(RAND_load_file(conf->random_file, 1024*1024))) {
                radlog(L_ERR, "rlm_eap: SSL error %s", ERR_error_string(ERR_get_error(), NULL));
                radlog(L_ERR, "rlm_eap_tls: Error loading randomness");
                return NULL;
        }

        /*
         * Set the cipher list if we were told to
         */
        if (conf->cipher_list) {
                if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) {
                        radlog(L_ERR, "rlm_eap_tls: Error setting cipher list");
                        return NULL;
                }
        }

        return ctx;
}


/*
 *      Detach the EAP-TLS module.
 */
static int eaptls_detach(void *arg)
{
        EAP_TLS_CONF     *conf;
        eap_tls_t        *inst;

        inst = (eap_tls_t *) arg;
        conf = inst->conf;

        if (conf) {
                memset(conf, 0, sizeof(*conf));
                free(inst->conf);
                inst->conf = NULL;
        }

        if (inst->ctx) SSL_CTX_free(inst->ctx);
        inst->ctx = NULL;

        free(inst);

        return 0;
}


/*
 *      Attach the EAP-TLS module.
 */
static int eaptls_attach(CONF_SECTION *cs, void **instance)
{
        EAP_TLS_CONF     *conf;
        eap_tls_t        *inst;

        /* Store all these values in the data structure for later references */
        inst = (eap_tls_t *)malloc(sizeof(*inst));
        if (!inst) {
                radlog(L_ERR, "rlm_eap_tls: out of memory");
                return -1;
        }
        memset(inst, 0, sizeof(*inst));

        /*
         *      Parse the config file & get all the configured values
         */
        conf = (EAP_TLS_CONF *)malloc(sizeof(*conf));
        if (conf == NULL) {
                free(inst);
                radlog(L_ERR, "rlm_eap_tls: out of memory");
                return -1;
        }
        memset(conf, 0, sizeof(*conf));

        inst->conf = conf;
        if (cf_section_parse(cs, conf, module_config) < 0) {
                eaptls_detach(inst);
                return -1;
        }

        /*
         *      The EAP RFC's say 1020, but we're less picky.
         */
        if (conf->fragment_size < 100) {
                radlog(L_ERR, "rlm_eap_tls: Fragment size is too small.");
                eaptls_detach(inst);
                return -1;
        }

        /*
         *      The maximum size for a RADIUS packet is 4096,
         *      minus the header (20), Message-Authenticator (18),
         *      and State (18), etc. results in about 4000 bytes of data
         *      that can be devoted *solely* to EAP.
         */
        if (conf->fragment_size > 4000) {
                radlog(L_ERR, "rlm_eap_tls: Fragment size is too large.");
                eaptls_detach(inst);
                return -1;
        }

        /*
         *      Account for the EAP header (4), and the EAP-TLS header
         *      (6), as per Section 4.2 of RFC 2716.  What's left is
         *      the maximum amount of data we read from a TLS buffer.
         */
        conf->fragment_size -= 10;

        /*
         *      This magic makes the administrators life HUGELY easier
         *      on initial deployments.
         *
         *      If the server starts up in debugging mode, AND the
         *      bootstrap command is configured, AND it exists, AND
         *      there is no server certificate
         */
        if (conf->make_cert_command && (debug_flag >= 2)) {
                struct stat buf;

                if ((stat(conf->make_cert_command, &buf) == 0) &&
                    (stat(conf->certificate_file, &buf) < 0) &&
                    (errno == ENOENT) &&
                    (radius_exec_program(conf->make_cert_command, NULL, 1,
                                         NULL, 0, NULL, NULL, 0) != 0)) {
                        eaptls_detach(inst);
                        return -1;
                }
        }


        /*
         *      Initialize TLS
         */
        inst->ctx = init_tls_ctx(conf);
        if (inst->ctx == NULL) {
                eaptls_detach(inst);
                return -1;
        }

        if (load_dh_params(inst->ctx, conf->dh_file) < 0) {
                eaptls_detach(inst);
                return -1;
        }

        *instance = inst;

        return 0;
}


/*
 *      Send an initial eap-tls request to the peer.
 *
 *      Frame eap reply packet.
 *      len = header + type + tls_typedata
 *      tls_typedata = flags(Start (S) bit set, and no data)
 *
 *      Once having received the peer's Identity, the EAP server MUST
 *      respond with an EAP-TLS/Start packet, which is an
 *      EAP-Request packet with EAP-Type=EAP-TLS, the Start (S) bit
 *      set, and no data.  The EAP-TLS conversation will then begin,
 *      with the peer sending an EAP-Response packet with
 *      EAP-Type = EAP-TLS.  The data field of that packet will
 *      be the TLS data.
 *
 *      Fragment length is Framed-MTU - 4.
 *
 *      http://mail.frascone.com/pipermail/public/eap/2003-July/001426.html
 */
static int eaptls_initiate(void *type_arg, EAP_HANDLER *handler)
{
        int             status;
        tls_session_t   *ssn;
        eap_tls_t       *inst;
        VALUE_PAIR      *vp;
        int             client_cert = TRUE;
        int             verify_mode = 0;

        inst = (eap_tls_t *)type_arg;

        /*
         *      If we're TTLS or PEAP, then do NOT require a client
         *      certificate.
         *
         *      FIXME: This should be more configurable.
         */
        if (handler->eap_type != PW_EAP_TLS) {
                vp = pairfind(handler->request->config_items,
                              PW_EAP_TLS_REQUIRE_CLIENT_CERT);
                if (!vp) {
                        client_cert = FALSE;
                } else {
                        client_cert = vp->vp_integer;
                }
        }

        /*
         *      Every new session is started only from EAP-TLS-START.
         *      Before Sending EAP-TLS-START, open a new SSL session.
         *      Create all the required data structures & store them
         *      in Opaque.  So that we can use these data structures
         *      when we get the response
         */
        ssn = eaptls_new_session(inst->ctx, client_cert);
        if (!ssn) {
                return 0;
        }

        /*
         *      Verify the peer certificate, if asked.
         */
        if (client_cert) {
                DEBUG2(" rlm_eap_tls: Requiring client certificate");
                verify_mode = SSL_VERIFY_PEER;
                verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
                verify_mode |= SSL_VERIFY_CLIENT_ONCE;
        }
        SSL_set_verify(ssn->ssl, verify_mode, cbtls_verify);

        /*
         *      Create a structure for all the items required to be
         *      verified for each client and set that as opaque data
         *      structure.
         *
         *      NOTE: If we want to set each item sepearately then
         *      this index should be global.
         */
        SSL_set_ex_data(ssn->ssl, 0, (void *)handler);
        SSL_set_ex_data(ssn->ssl, 1, (void *)inst->conf);

        ssn->length_flag = inst->conf->include_length;

        /*
         *      We use default fragment size, unless the Framed-MTU
         *      tells us it's too big.  Note that we do NOT account
         *      for the EAP-TLS headers if conf->fragment_size is
         *      large, because that config item looks to be confusing.
         *
         *      i.e. it should REALLY be called MTU, and the code here
         *      should figure out what that means for TLS fragment size.
         *      asking the administrator to know the internal details
         *      of EAP-TLS in order to calculate fragment sizes is
         *      just too much.
         */
        ssn->offset = inst->conf->fragment_size;
        vp = pairfind(handler->request->packet->vps, PW_FRAMED_MTU);
        if (vp && ((vp->vp_integer - 14) < ssn->offset)) {
                /*
                 *      Discount the Framed-MTU by:
                 *       4 : EAPOL header
                 *       4 : EAP header (code + id + length)
                 *       1 : EAP type == EAP-TLS
                 *       1 : EAP-TLS Flags
                 *       4 : EAP-TLS Message length
                 *          (even if conf->include_length == 0,
                 *           just to be lazy).
                 *      ---
                 *      14
                 */
                ssn->offset = vp->vp_integer - 14;
        }

        handler->opaque = ((void *)ssn);
        handler->free_opaque = session_free;

        DEBUG2("  rlm_eap_tls: Initiate");

        /*
         *      PEAP-specific breakage.
         */
        if (handler->eap_type == PW_EAP_PEAP) {
                /*
                 *      As it is a poorly designed protocol, PEAP uses
                 *      bits in the TLS header to indicate PEAP
                 *      version numbers.  For now, we only support
                 *      PEAP version 0, so it doesn't matter too much.
                 *      However, if we support later versions of PEAP,
                 *      we will need this flag to indicate which
                 *      version we're currently dealing with.
                 */
                ssn->peap_flag = 0x00;

                /*
                 *      PEAP version 0 requires 'include_length = no',
                 *      so rather than hoping the user figures it out,
                 *      we force it here.
                 */
                ssn->length_flag = 0;
        }

        /*
         *      TLS session initialization is over.  Now handle TLS
         *      related handshaking or application data.
         */
        status = eaptls_start(handler->eap_ds, ssn->peap_flag);
        DEBUG2("  rlm_eap_tls: Start returned %d", status);
        if (status == 0)
                return 0;

        /*
         *      The next stage to process the packet.
         */
        handler->stage = AUTHENTICATE;

        return 1;
}

/*
 *      Do authentication, by letting EAP-TLS do most of the work.
 */
static int eaptls_authenticate(void *arg UNUSED, EAP_HANDLER *handler)
{
        eaptls_status_t status;
        tls_session_t *tls_session = (tls_session_t *) handler->opaque;

        DEBUG2("  rlm_eap_tls: Authenticate");

        status = eaptls_process(handler);
        DEBUG2("  eaptls_process returned %d\n", status);
        switch (status) {
                /*
                 *      EAP-TLS handshake was successful, return an
                 *      EAP-TLS-Success packet here.
                 */
        case EAPTLS_SUCCESS:
                break;

                /*
                 *      The TLS code is still working on the TLS
                 *      exchange, and it's a valid TLS request.
                 *      do nothing.
                 */
        case EAPTLS_HANDLED:
                return 1;

                /*
                 *      Handshake is done, proceed with decoding tunneled
                 *      data.
                 */
        case EAPTLS_OK:
                DEBUG2("  rlm_eap_tls: Received unexpected tunneled data after successful handshake.");
#ifndef NDEBUG
                if ((debug_flag > 2) && fr_log_fp) {
                        unsigned int i;
                        unsigned int data_len;
                        unsigned char buffer[1024];

                        data_len = (tls_session->record_minus)(&tls_session->dirty_in,
                                                buffer, sizeof(buffer));
                        log_debug("  Tunneled data (%u bytes)\n", data_len);
                        for (i = 0; i < data_len; i++) {
                                if ((i & 0x0f) == 0x00) fprintf(fr_log_fp, "  %x: ", i);
                                if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n");

                                fprintf(fr_log_fp, "%02x ", buffer[i]);
                        }
                        fprintf(fr_log_fp, "\n");
                }
#endif

                eaptls_fail(handler->eap_ds, 0);
                return 0;
                break;

                /*
                 *      Anything else: fail.
                 */
        default:
                return 0;
        }

        /*
         *      Success: Return MPPE keys.
         */
        eaptls_success(handler->eap_ds, 0);
        eaptls_gen_mppe_keys(&handler->request->reply->vps,
                             tls_session->ssl,
                             "client EAP encryption");
        return 1;
}

/*
 *      The module name should be the only globally exported symbol.
 *      That is, everything else should be 'static'.
 */
EAP_TYPE rlm_eap_tls = {
        "eap_tls",
        eaptls_attach,                  /* attach */
        eaptls_initiate,                /* Start the initial request */
        NULL,                           /* authorization */
        eaptls_authenticate,            /* authentication */
        eaptls_detach                   /* detach */
};




More information about the Freeradius-Devel mailing list