Logo Search packages:      
Sourcecode: radsecproxy version File versions  Download package

tlscommon.c

/*
 * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 */

#if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#ifdef SYS_SOLARIS9
#include <fcntl.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <ctype.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <regex.h>
#include <libgen.h>
#include <pthread.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/md5.h>
#include <openssl/x509v3.h>
#include "debug.h"
#include "list.h"
#include "hash.h"
#include "util.h"
#include "hostport.h"
#include "radsecproxy.h"

static struct hash *tlsconfs = NULL;

static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) {
    int pwdlen = strlen(userdata);
    if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */
      return 0;
    memcpy(buf, userdata, pwdlen);
    return pwdlen;
}

static int verify_cb(int ok, X509_STORE_CTX *ctx) {
    char *buf = NULL;
    X509 *err_cert;
    int err, depth;

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

    if (depth > MAX_CERT_DEPTH) {
      ok = 0;
      err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
      X509_STORE_CTX_set_error(ctx, err);
    }

    if (!ok) {
      if (err_cert)
          buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0);
      debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : "");
      free(buf);
      buf = NULL;

      switch (err) {
      case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
          if (err_cert) {
            buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0);
            if (buf) {
                debug(DBG_WARN, "\tIssuer=%s", buf);
                free(buf);
                buf = NULL;
            }
          }
          break;
      case X509_V_ERR_CERT_NOT_YET_VALID:
      case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
          debug(DBG_WARN, "\tCertificate not yet valid");
          break;
      case X509_V_ERR_CERT_HAS_EXPIRED:
          debug(DBG_WARN, "Certificate has expired");
          break;
      case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
          debug(DBG_WARN, "Certificate no longer valid (after notAfter)");
          break;
      case X509_V_ERR_NO_EXPLICIT_POLICY:
          debug(DBG_WARN, "No Explicit Certificate Policy");
          break;
      }
    }
#ifdef DEBUG
    printf("certificate verify returns %d\n", ok);
#endif
    return ok;
}

#ifdef DEBUG
static void ssl_info_callback(const SSL *ssl, int where, int ret) {
    const char *s;
    int w;

    w = where & ~SSL_ST_MASK;

    if (w & SSL_ST_CONNECT)
      s = "SSL_connect";
    else if (w & SSL_ST_ACCEPT)
      s = "SSL_accept";
    else
      s = "undefined";

    if (where & SSL_CB_LOOP)
      debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl));
    else if (where & SSL_CB_ALERT) {
      s = (where & SSL_CB_READ) ? "read" : "write";
      debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
    }
    else if (where & SSL_CB_EXIT) {
      if (ret == 0)
          debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl));
      else if (ret < 0)
          debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl));
    }
}
#endif

static X509_VERIFY_PARAM *createverifyparams(char **poids) {
    X509_VERIFY_PARAM *pm;
    ASN1_OBJECT *pobject;
    int i;

    pm = X509_VERIFY_PARAM_new();
    if (!pm)
      return NULL;

    for (i = 0; poids[i]; i++) {
      pobject = OBJ_txt2obj(poids[i], 0);
      if (!pobject) {
          X509_VERIFY_PARAM_free(pm);
          return NULL;
      }
      X509_VERIFY_PARAM_add0_policy(pm, pobject);
    }

    X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY);
    return pm;
}

static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) {
    STACK_OF(X509_NAME) *calist;
    X509_STORE *x509_s;
    unsigned long error;

    if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) {
      while ((error = ERR_get_error()))
          debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
      debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name);
      return 0;
    }

    calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL;
    if (!conf->cacertfile || calist) {
      if (conf->cacertpath) {
          if (!calist)
            calist = sk_X509_NAME_new_null();
          if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) {
            sk_X509_NAME_free(calist);
            calist = NULL;
          }
      }
    }
    if (!calist) {
      while ((error = ERR_get_error()))
          debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
      debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name);
      return 0;
    }
    ERR_clear_error(); /* add_dir_cert_subj returns errors on success */
    SSL_CTX_set_client_CA_list(ctx, calist);

    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
    SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1);

    if (conf->crlcheck || conf->vpm) {
      x509_s = SSL_CTX_get_cert_store(ctx);
      if (conf->crlcheck)
          X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
      if (conf->vpm)
          X509_STORE_set1_param(x509_s, conf->vpm);
    }

    debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name);
    return 1;
}

static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) {
    SSL_CTX *ctx = NULL;
    unsigned long error;

    switch (type) {
#ifdef RADPROT_TLS
    case RAD_TLS:
      ctx = SSL_CTX_new(TLSv1_method());
#ifdef DEBUG
      SSL_CTX_set_info_callback(ctx, ssl_info_callback);
#endif
      break;
#endif
#ifdef RADPROT_DTLS
    case RAD_DTLS:
      ctx = SSL_CTX_new(DTLSv1_method());
#ifdef DEBUG
      SSL_CTX_set_info_callback(ctx, ssl_info_callback);
#endif
      SSL_CTX_set_read_ahead(ctx, 1);
      break;
#endif
    }
    if (!ctx) {
      debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
      return NULL;
    }

    if (conf->certkeypwd) {
      SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd);
      SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
    }
    if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) ||
      !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) ||
      !SSL_CTX_check_private_key(ctx)) {
      while ((error = ERR_get_error()))
          debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
      debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
      SSL_CTX_free(ctx);
      return NULL;
    }

    if (conf->policyoids) {
      if (!conf->vpm) {
          conf->vpm = createverifyparams(conf->policyoids);
          if (!conf->vpm) {
            debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name);
            SSL_CTX_free(ctx);
            return NULL;
          }
      }
    }

    if (!tlsaddcacrl(ctx, conf)) {
      if (conf->vpm) {
          X509_VERIFY_PARAM_free(conf->vpm);
          conf->vpm = NULL;
      }
      SSL_CTX_free(ctx);
      return NULL;
    }

    debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name);
    return ctx;
}

struct tls *tlsgettls(char *alt1, char *alt2) {
    struct tls *t;

    t = hash_read(tlsconfs, alt1, strlen(alt1));
    if (!t)
      t = hash_read(tlsconfs, alt2, strlen(alt2));
    return t;
}

SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) {
    struct timeval now;

    if (!t)
      return NULL;
    gettimeofday(&now, NULL);

    switch (type) {
#ifdef RADPROT_TLS
    case RAD_TLS:
      if (t->tlsexpiry && t->tlsctx) {
          if (t->tlsexpiry < now.tv_sec) {
            t->tlsexpiry = now.tv_sec + t->cacheexpiry;
            tlsaddcacrl(t->tlsctx, t);
          }
      }
      if (!t->tlsctx) {
          t->tlsctx = tlscreatectx(RAD_TLS, t);
          if (t->cacheexpiry)
            t->tlsexpiry = now.tv_sec + t->cacheexpiry;
      }
      return t->tlsctx;
#endif
#ifdef RADPROT_DTLS
    case RAD_DTLS:
      if (t->dtlsexpiry && t->dtlsctx) {
          if (t->dtlsexpiry < now.tv_sec) {
            t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
            tlsaddcacrl(t->dtlsctx, t);
          }
      }
      if (!t->dtlsctx) {
          t->dtlsctx = tlscreatectx(RAD_DTLS, t);
          if (t->cacheexpiry)
            t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
      }
      return t->dtlsctx;
#endif
    }
    return NULL;
}

X509 *verifytlscert(SSL *ssl) {
    X509 *cert;
    unsigned long error;

    if (SSL_get_verify_result(ssl) != X509_V_OK) {
      debug(DBG_ERR, "verifytlscert: basic validation failed");
      while ((error = ERR_get_error()))
          debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL));
      return NULL;
    }

    cert = SSL_get_peer_certificate(ssl);
    if (!cert)
      debug(DBG_ERR, "verifytlscert: failed to obtain certificate");
    return cert;
}

static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) {
    int loc, i, l, n, r = 0;
    char *v;
    X509_EXTENSION *ex;
    STACK_OF(GENERAL_NAME) *alt;
    GENERAL_NAME *gn;

    debug(DBG_DBG, "subjectaltnameaddr");

    loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
    if (loc < 0)
      return r;

    ex = X509_get_ext(cert, loc);
    alt = X509V3_EXT_d2i(ex);
    if (!alt)
      return r;

    n = sk_GENERAL_NAME_num(alt);
    for (i = 0; i < n; i++) {
      gn = sk_GENERAL_NAME_value(alt, i);
      if (gn->type != GEN_IPADD)
          continue;
      r = -1;
      v = (char *)ASN1_STRING_data(gn->d.ia5);
      l = ASN1_STRING_length(gn->d.ia5);
      if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr)))
          && !memcmp(v, &addr, l)) {
          r = 1;
          break;
      }
    }
    GENERAL_NAMES_free(alt);
    return r;
}

static int subjectaltnameregexp(X509 *cert, int type, char *exact,  regex_t *regex) {
    int loc, i, l, n, r = 0;
    char *s, *v;
    X509_EXTENSION *ex;
    STACK_OF(GENERAL_NAME) *alt;
    GENERAL_NAME *gn;

    debug(DBG_DBG, "subjectaltnameregexp");

    loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
    if (loc < 0)
      return r;

    ex = X509_get_ext(cert, loc);
    alt = X509V3_EXT_d2i(ex);
    if (!alt)
      return r;

    n = sk_GENERAL_NAME_num(alt);
    for (i = 0; i < n; i++) {
      gn = sk_GENERAL_NAME_value(alt, i);
      if (gn->type != type)
          continue;
      r = -1;
      v = (char *)ASN1_STRING_data(gn->d.ia5);
      l = ASN1_STRING_length(gn->d.ia5);
      if (l <= 0)
          continue;
#ifdef DEBUG
      printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l);
#endif
      if (exact) {
          if (memcmp(v, exact, l))
            continue;
      } else {
          s = stringcopy((char *)v, l);
          if (!s) {
            debug(DBG_ERR, "malloc failed");
            continue;
          }
          if (regexec(regex, s, 0, NULL, 0)) {
            free(s);
            continue;
          }
          free(s);
      }
      r = 1;
      break;
    }
    GENERAL_NAMES_free(alt);
    return r;
}

static int cnregexp(X509 *cert, char *exact, regex_t *regex) {
    int loc, l;
    char *v, *s;
    X509_NAME *nm;
    X509_NAME_ENTRY *e;
    ASN1_STRING *t;

    nm = X509_get_subject_name(cert);
    loc = -1;
    for (;;) {
      loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc);
      if (loc == -1)
          break;
      e = X509_NAME_get_entry(nm, loc);
      t = X509_NAME_ENTRY_get_data(e);
      v = (char *) ASN1_STRING_data(t);
      l = ASN1_STRING_length(t);
      if (l < 0)
          continue;
      if (exact) {
          if (l == strlen(exact) && !strncasecmp(exact, v, l))
            return 1;
      } else {
          s = stringcopy((char *)v, l);
          if (!s) {
            debug(DBG_ERR, "malloc failed");
            continue;
          }
          if (regexec(regex, s, 0, NULL, 0)) {
            free(s);
            continue;
          }
          free(s);
          return 1;
      }
    }
    return 0;
}

/* this is a bit sloppy, should not always accept match to any */
int certnamecheck(X509 *cert, struct list *hostports) {
    struct list_node *entry;
    struct hostportres *hp;
    int r;
    uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */
    struct in6_addr addr;

    for (entry = list_first(hostports); entry; entry = list_next(entry)) {
      hp = (struct hostportres *)entry->data;
      if (hp->prefixlen != 255) {
          /* we disable the check for prefixes */
          return 1;
      }
      if (inet_pton(AF_INET, hp->host, &addr))
          type = AF_INET;
      else if (inet_pton(AF_INET6, hp->host, &addr))
          type = AF_INET6;
      else
          type = 0;

      r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL);
      if (r) {
          if (r > 0) {
            debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host);
            return 1;
          }
          debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host);
      } else {
          if (cnregexp(cert, hp->host, NULL)) {
            debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host);
            return 1;
          }
          debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host);
      }
    }
    return 0;
}

int verifyconfcert(X509 *cert, struct clsrvconf *conf) {
    if (conf->certnamecheck) {
      if (!certnamecheck(cert, conf->hostports)) {
          debug(DBG_WARN, "verifyconfcert: certificate name check failed");
          return 0;
      }
      debug(DBG_WARN, "verifyconfcert: certificate name check ok");
    }
    if (conf->certcnregex) {
      if (cnregexp(cert, NULL, conf->certcnregex) < 1) {
          debug(DBG_WARN, "verifyconfcert: CN not matching regex");
          return 0;
      }
      debug(DBG_DBG, "verifyconfcert: CN matching regex");
    }
    if (conf->certuriregex) {
      if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) {
          debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex");
          return 0;
      }
      debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex");
    }
    return 1;
}

int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
    struct tls *conf;
    long int expiry = LONG_MIN;

    debug(DBG_DBG, "conftls_cb called for %s", block);

    conf = malloc(sizeof(struct tls));
    if (!conf) {
      debug(DBG_ERR, "conftls_cb: malloc failed");
      return 0;
    }
    memset(conf, 0, sizeof(struct tls));

    if (!getgenericconfig(cf, block,
                    "CACertificateFile", CONF_STR, &conf->cacertfile,
                    "CACertificatePath", CONF_STR, &conf->cacertpath,
                    "CertificateFile", CONF_STR, &conf->certfile,
                    "CertificateKeyFile", CONF_STR, &conf->certkeyfile,
                    "CertificateKeyPassword", CONF_STR, &conf->certkeypwd,
                    "CacheExpiry", CONF_LINT, &expiry,
                    "CRLCheck", CONF_BLN, &conf->crlcheck,
                    "PolicyOID", CONF_MSTR, &conf->policyoids,
                    NULL
          )) {
      debug(DBG_ERR, "conftls_cb: configuration error in block %s", val);
      goto errexit;
    }
    if (!conf->certfile || !conf->certkeyfile) {
      debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val);
      goto errexit;
    }
    if (!conf->cacertfile && !conf->cacertpath) {
      debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val);
      goto errexit;
    }
    if (expiry != LONG_MIN) {
      if (expiry < 0) {
          debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry);
          goto errexit;
      }
      conf->cacheexpiry = expiry;
    }

    conf->name = stringcopy(val, 0);
    if (!conf->name) {
      debug(DBG_ERR, "conftls_cb: malloc failed");
      goto errexit;
    }

    if (!tlsconfs)
      tlsconfs = hash_create();
    if (!hash_insert(tlsconfs, val, strlen(val), conf)) {
      debug(DBG_ERR, "conftls_cb: malloc failed");
      goto errexit;
    }
    if (!tlsgetctx(RAD_TLS, conf))
      debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val);
    debug(DBG_DBG, "conftls_cb: added TLS block %s", val);
    return 1;

errexit:
    free(conf->cacertfile);
    free(conf->cacertpath);
    free(conf->certfile);
    free(conf->certkeyfile);
    free(conf->certkeypwd);
    freegconfmstr(conf->policyoids);
    free(conf);
    return 0;
}

int addmatchcertattr(struct clsrvconf *conf) {
    char *v;
    regex_t **r;

    if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) {
      r = &conf->certcnregex;
      v = conf->matchcertattr + 4;
    } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) {
      r = &conf->certuriregex;
      v = conf->matchcertattr + 20;
    } else
      return 0;
    if (!*v)
      return 0;
    /* regexp, remove optional trailing / if present */
    if (v[strlen(v) - 1] == '/')
      v[strlen(v) - 1] = '\0';
    if (!*v)
      return 0;

    *r = malloc(sizeof(regex_t));
    if (!*r) {
      debug(DBG_ERR, "malloc failed");
      return 0;
    }
    if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
      free(*r);
      *r = NULL;
      debug(DBG_ERR, "failed to compile regular expression %s", v);
      return 0;
    }
    return 1;
}
#else
/* Just to makes file non-empty, should rather avoid compiling this file when not needed */
static void tlsdummy() {
}
#endif

/* Local Variables: */
/* c-file-style: "stroustrup" */
/* End: */

Generated by  Doxygen 1.6.0   Back to index