From: Alan T. DeKok Date: Wed, 8 Mar 2017 22:12:24 +0000 (-0500) Subject: more checks for client certificate expiration X-Git-Tag: release_3_0_14~89 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d35e9b3549fc75938316336031f47709c82d1d00;p=thirdparty%2Ffreeradius-server.git more checks for client certificate expiration --- diff --git a/src/main/tls.c b/src/main/tls.c index 6b3291a01f5..4fa25d6ae82 100644 --- a/src/main/tls.c +++ b/src/main/tls.c @@ -1426,6 +1426,61 @@ error: return 0; } +/** Convert OpenSSL's ASN1_TIME to an epoch time + * + * @param[out] out Where to write the time_t. + * @param[in] asn1 The ASN1_TIME to convert. + * @return + * - 0 success. + * - -1 on failure. + */ +static int ocsp_asn1time_to_epoch(time_t *out, char const *asn1) +{ + struct tm t; + char const *p = asn1, *end = p + strlen(p); + + memset(&t, 0, sizeof(t)); + + if ((end - p) <= 12) { + if ((end - p) < 2) { + fr_strerror_printf("ASN1 date string too short, expected 2 additional bytes, got %zu bytes", + end - p); + return -1; + } + + t.tm_year = (*(p++) - '0') * 10; + t.tm_year += (*(p++) - '0'); + if (t.tm_year < 70) t.tm_year += 100; + } else { + t.tm_year = (*(p++) - '0') * 1000; + t.tm_year += (*(p++) - '0') * 100; + t.tm_year += (*(p++) - '0') * 10; + t.tm_year += (*(p++) - '0'); + t.tm_year -= 1900; + } + + if ((end - p) < 10) { + fr_strerror_printf("ASN1 string too short, expected 10 additional bytes, got %zu bytes", + end - p); + return -1; + } + + t.tm_mon = (*(p++) - '0') * 10; + t.tm_mon += (*(p++) - '0') - 1; // -1 since January is 0 not 1. + t.tm_mday = (*(p++) - '0') * 10; + t.tm_mday += (*(p++) - '0'); + t.tm_hour = (*(p++) - '0') * 10; + t.tm_hour += (*(p++) - '0'); + t.tm_min = (*(p++) - '0') * 10; + t.tm_min += (*(p++) - '0'); + t.tm_sec = (*(p++) - '0') * 10; + t.tm_sec += (*(p++) - '0'); + + /* Apparently OpenSSL converts all timestamps to UTC? Maybe? */ + *out = timegm(&t); + return 0; +} + #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) static SSL_SESSION *cbtls_get_session(SSL *ssl, unsigned char *data, int len, int *copy) #else @@ -1472,27 +1527,28 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l struct stat st; VALUE_PAIR *vps = NULL; + VALUE_PAIR *vp; /* load the actual SSL session */ snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); fd = open(filename, O_RDONLY); if (fd < 0) { RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno)); - goto err; + goto error; } rv = fstat(fd, &st); if (rv < 0) { RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); close(fd); - goto err; + goto error; } sess_data = talloc_array(NULL, unsigned char, st.st_size); if (!sess_data) { RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); close(fd); - goto err; + goto error; } q = sess_data; @@ -1502,7 +1558,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l if (rv < 1) { RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno)); close(fd); - goto err; + goto error; } todo -= rv; q += rv; @@ -1525,7 +1581,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l sess = d2i_SSL_SESSION(NULL, o, st.st_size); if (!sess) { RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); - goto err; + goto error; } /* read in the cached VPs from the .vps file */ @@ -1535,7 +1591,40 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l if (rv < 0) { /* not safe to un-persist a session w/o VPs */ RWDEBUG("Failed loading persisted VPs for session %s", buffer); - goto err; + SSL_SESSION_free(sess); + goto error; + } + + /* + * Enforce client certificate expiration. + */ + vp = fr_pair_find_by_num(pairlist->reply, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY); + if (vp) { + time_t expires; + + if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { + RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s", buffer); + SSL_SESSION_free(sess); + goto error; + } + + if (expires <= request->timestamp) { + RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer); + SSL_SESSION_free(sess); + goto error; + } + + /* + * Account for Session-Timeout, if it's available. + */ + vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); + if (vp) { + if ((request->timestamp + vp->vp_integer) > expires) { + vp->vp_integer = expires - request->timestamp; + RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", + vp->vp_integer); + } + } } /* move the cached VPs into the session */ @@ -1545,7 +1634,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l RWDEBUG("Successfully restored session %s", buffer); rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:"); } -err: +error: if (sess_data) talloc_free(sess_data); if (pairlist) pairlist_free(&pairlist); @@ -3139,60 +3228,6 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs) return conf; } -/** Convert OpenSSL's ASN1_TIME to an epoch time - * - * @param[out] out Where to write the time_t. - * @param[in] asn1 The ASN1_TIME to convert. - * @return - * - 0 success. - * - -1 on failure. - */ -static int ocsp_asn1time_to_epoch(time_t *out, char const *asn1) -{ - struct tm t; - char const *p = asn1, *end = p + strlen(p); - - memset(&t, 0, sizeof(t)); - - if ((end - p) <= 12) { - if ((end - p) < 2) { - fr_strerror_printf("ASN1 date string too short, expected 2 additional bytes, got %zu bytes", - end - p); - return -1; - } - - t.tm_year = (*(p++) - '0') * 10; - t.tm_year += (*(p++) - '0'); - if (t.tm_year < 70) t.tm_year += 100; - } else { - t.tm_year = (*(p++) - '0') * 1000; - t.tm_year += (*(p++) - '0') * 100; - t.tm_year += (*(p++) - '0') * 10; - t.tm_year += (*(p++) - '0'); - t.tm_year -= 1900; - } - - if ((end - p) < 10) { - fr_strerror_printf("ASN1 string too short, expected 10 additional bytes, got %zu bytes", - end - p); - return -1; - } - - t.tm_mon = (*(p++) - '0') * 10; - t.tm_mon += (*(p++) - '0') - 1; // -1 since January is 0 not 1. - t.tm_mday = (*(p++) - '0') * 10; - t.tm_mday += (*(p++) - '0'); - t.tm_hour = (*(p++) - '0') * 10; - t.tm_hour += (*(p++) - '0'); - t.tm_min = (*(p++) - '0') * 10; - t.tm_min += (*(p++) - '0'); - t.tm_sec = (*(p++) - '0') * 10; - t.tm_sec += (*(p++) - '0'); - - /* Apparently OpenSSL converts all timestamps to UTC? Maybe? */ - *out = timegm(&t); - return 0; -} int tls_success(tls_session_t *ssn, REQUEST *request) { @@ -3272,7 +3307,6 @@ int tls_success(tls_session_t *ssn, REQUEST *request) */ fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); - vp = fr_pair_find_by_num(request->packet->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY); if (vp) { time_t expires;