Checking TLS-Cert-* and and accept/reject based on them
Hi, An question came up recently on freeradius-users whether it was possible to reject a request based on the TLS-Client-Cert-* type attributes. The reply being that it would be quite hard to allow these checks to happen in the authorize section of the code. I had a look at doing that, but it does seem pretty nasty, as it would involve doing EAP-TLS things at a different place from, say PEAP or TTLS. At least, from what I could tell. However, I looked at and implemented a different solution, which seems to work really well. Taking ideas from inner-tunnels and the SoH check method, I added a virtual_server option to the tls configuration. The rlm_eap_tls code then, just before returning success, runs through this virtual server (if given) allowing it to check the TLS-Cert* vps, and reject if it wants to based on them. It turns out to be a pretty small patch, and the code path isn't touched at all if the virtual_server option is not set. I've tested with EAP-TLS and PEAP/EAP-TLS, and both work well. I think the next step would be to move the client certificate validation, ocsp and other checks as 'modules' in this virtual server, but that looks a bit harder as they are done in a openssl callback function (cbtls_verify IIRC) - but may not be past the realms of possibility. Patch below, and also pushed to github: https://github.com/mcnewton/freeradius-server/commit/fbee1e9b4ce93c15d0f074a... Comments? Cheers, Matthew
From fbee1e9b4ce93c15d0f074ad3fdfb71ba095a4ed Mon Sep 17 00:00:00 2001 From: Matthew Newton <mcn4@leicester.ac.uk> Date: Thu, 2 Feb 2012 16:00:37 +0000 Subject: [PATCH] Add virtual-server option for EAP-TLS to allow certificate field checks
Normally attributes such as TLS-Client-Cert-Common-Name can be seen in Post-Auth only, which is too late to act if the return to the client should be changed. This code adds a virtual-server option to EAP-TLS to allow these values to be examined, and the return status updated accordingly. --- raddb/eap.conf | 8 ++ raddb/sites-available/check-eap-tls | 54 +++++++++++++++ .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c | 69 +++++++++++++++++++- .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h | 2 + 4 files changed, 132 insertions(+), 1 deletions(-) create mode 100644 raddb/sites-available/check-eap-tls diff --git a/raddb/eap.conf b/raddb/eap.conf index 087f7f7..81d9074 100644 --- a/raddb/eap.conf +++ b/raddb/eap.conf @@ -291,6 +291,14 @@ ecdh_curve = "prime256v1" # + # As part of checking a client certificate, the EAP-TLS + # sets some attributes such as TLS-Client-Cert-CN. This + # virtual server has access to these attributes, and can + # be used to accept or reject the request. + # + # virtual_server = check-eap-tls + + # # Session resumption / fast reauthentication # cache. # diff --git a/raddb/sites-available/check-eap-tls b/raddb/sites-available/check-eap-tls new file mode 100644 index 0000000..15dc628 --- /dev/null +++ b/raddb/sites-available/check-eap-tls @@ -0,0 +1,54 @@ +# This virtual server allows EAP-TLS to reject access requests +# based on some certificate attributes. + +# Value-pairs that are available for checking include: +# +# TLS-Client-Cert-Subject +# TLS-Client-Cert-Issuer +# TLS-Client-Cert-Common-Name +# TLS-Client-Cert-Subject-Alt-Name-Email +# +# To see a full list of attributes, run the server in debug mode +# with this virtual server configured, and look at the attributes +# passed in to this virtual server. + +server check-eap-tls { + + +# Authorize - this is the only section required. +# +# To accept the access request, set Auth-Type = Accept, otherwise +# set it to Reject. + +authorize { + if ("%{TLS-Client-Cert-Common-Name}" == "client.example.com") { + update config { + Auth-Type = Accept + } + } + else { + update config { + Auth-Type = Reject + } + update reply { + Reply-Message = "Your certificate is not valid." + } + } + +# if ("host/%{TLS-Client-Cert-Common-Name}" == "%{User-Name}") { +# update config { +# Auth-Type = Accept +# } +# } +# else { +# update config { +# Auth-Type = Reject +# } +# } + +} + + + +} + diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c index cbe61ad..2ffd6eb 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c @@ -130,6 +130,8 @@ static CONF_PARSER module_config[] = { 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}, + { "virtual_server", PW_TYPE_STRING_PTR, + offsetof(EAP_TLS_CONF, virtual_server), NULL, NULL}, #if OPENSSL_VERSION_NUMBER >= 0x0090800fL #ifndef OPENSSL_NO_ECDH @@ -1611,6 +1613,7 @@ static int eaptls_authenticate(void *arg, EAP_HANDLER *handler) tls_session_t *tls_session = (tls_session_t *) handler->opaque; REQUEST *request = handler->request; eap_tls_t *inst = (eap_tls_t *) arg; + EAP_TLS_CONF *conf = inst->conf; RDEBUG2("Authenticate"); @@ -1619,9 +1622,73 @@ static int eaptls_authenticate(void *arg, EAP_HANDLER *handler) switch (status) { /* * EAP-TLS handshake was successful, return an - * EAP-TLS-Success packet here. + * EAP-TLS-Success packet here. If a virtual-server + * is configured, check that is happy, first. */ case EAPTLS_SUCCESS: + if (conf->virtual_server) { + VALUE_PAIR *vp; + VALUE_PAIR *copy; + REQUEST *fake; + + /* create a fake request, and copy current request vps in */ + fake = request_alloc_fake(request); + rad_assert(fake->packet->vps == NULL); + + vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); + if (vp) { + pairadd(&fake->packet->vps, vp); + } + + for (vp = request->packet->vps; vp != NULL; vp = vp->next) { + if (pairfind(fake->packet->vps, vp->attribute)) { + continue; + } + copy = paircopy2(vp, vp->attribute); + pairadd(&fake->packet->vps, copy); + } + + /* copy the TLS verification vps in to the request */ + for (vp = handler->certs; vp != NULL; vp = vp->next) { + if (pairfind(fake->packet->vps, vp->attribute)) { + continue; + } + copy = paircopy2(vp, vp->attribute); + pairadd(&fake->packet->vps, copy); + } + + /* set the virtual server to use */ + if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER)) != NULL) { + fake->server = vp->vp_strvalue; + } else { + fake->server = conf->virtual_server; + } + + RDEBUG("Processing EAP-TLS check:"); + debug_pair_list(fake->packet->vps); + + RDEBUG("server %s {", fake->server); + + rad_authenticate(fake); + + RDEBUG("} # server %s", fake->server); + + /* copy the reply vps back to our reply */ + pairadd(&request->reply->vps, fake->reply->vps); + fake->reply->vps = NULL; + + /* reject if virtual server didn't return accept */ + if (fake->reply->code != PW_AUTHENTICATION_ACK) { + RDEBUG2("EAP-TLS rejected by virtual server"); + request_free(&fake); + eaptls_fail(handler, 0); + return 0; + } + + request_free(&fake); + /* success */ + } + break; /* diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h index 34c917f..6840714 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h @@ -71,6 +71,8 @@ typedef struct eap_tls_conf { char *verify_tmp_dir; char *verify_client_cert_cmd; + char *virtual_server; + #ifdef HAVE_OPENSSL_OCSP_H /* * OCSP Configuration -- 1.7.2.5 -- Matthew Newton, Ph.D. <mcn4@le.ac.uk> Systems Architect (UNIX and Networks), Network Services, I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom For IT help contact helpdesk extn. 2253, <ithelp@le.ac.uk>
On 02/02/2012 04:27 PM, Matthew Newton wrote:
However, I looked at and implemented a different solution, which seems to work really well. Taking ideas from inner-tunnels and the SoH check method, I added a virtual_server option to the tls configuration. The rlm_eap_tls code then, just before returning success, runs through this virtual server (if given) allowing it to check the TLS-Cert* vps, and reject if it wants to based on them.
Comments?
Personally, I like the look of this approach; it's very closely analogous to how the tunneled mechanisms work. It might also be a good place to conditionally permit/deny TLS session resumption, which at the moment is a bit tricky. I'm not sure whether it makes sense to do cert validation / OSCP in an "inner" tunnel; I suspect that would be quite hard, and require a much larger patch. But I don't use either feature, so will withhold judgement.
Matthew Newton wrote:
However, I looked at and implemented a different solution, which seems to work really well. Taking ideas from inner-tunnels and the SoH check method, I added a virtual_server option to the tls configuration. The rlm_eap_tls code then, just before returning success, runs through this virtual server (if given) allowing it to check the TLS-Cert* vps, and reject if it wants to based on them.
That's very nice.
It turns out to be a pretty small patch, and the code path isn't touched at all if the virtual_server option is not set. I've tested with EAP-TLS and PEAP/EAP-TLS, and both work well.
I've posted some comments on github.
I think the next step would be to move the client certificate validation, ocsp and other checks as 'modules' in this virtual server, but that looks a bit harder as they are done in a openssl callback function (cbtls_verify IIRC) - but may not be past the realms of possibility.
I think that's a good idea. Especially the OCSP code. It should really be a pluggable module. Alan DeKok.
Hi, On Fri, Feb 03, 2012 at 11:25:30AM +0100, Alan DeKok wrote:
Matthew Newton wrote:
However, I looked at and implemented a different solution, which seems to work really well. Taking ideas from inner-tunnels and the SoH check method, I added a virtual_server option to the tls
That's very nice.
Thanks :)
It turns out to be a pretty small patch, and the code path isn't touched at all if the virtual_server option is not set. I've tested with EAP-TLS and PEAP/EAP-TLS, and both work well.
I've posted some comments on github.
I've improved it a lot based on that - it's much neater and simpler now. I realised that I was needlessly copying in the certificate VPs that had already been copied, so that's gone.
I think the next step would be to move the client certificate validation, ocsp and other checks as 'modules' in this virtual server, but that looks a bit harder as they are done in a openssl callback function (cbtls_verify IIRC) - but may not be past the realms of possibility.
I think that's a good idea. Especially the OCSP code. It should really be a pluggable module.
Will have to stare at the code on this. I struggle to understand the SSL stuff well, and this looks slightly more complicated due to the callback function, so I'm not sure where OpenSSL calls it, or if it could easily be separated out. Will try and have a look. Couple of updates pushed to https://github.com/mcnewton/freeradius-server/commits/patch-eaptls-vs Cheers, Matthew -- Matthew Newton, Ph.D. <mcn4@le.ac.uk> Systems Architect (UNIX and Networks), Network Services, I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom For IT help contact helpdesk extn. 2253, <ithelp@le.ac.uk>
Hi, On Fri, Feb 03, 2012 at 12:52:20PM +0000, Matthew Newton wrote:
On Fri, Feb 03, 2012 at 11:25:30AM +0100, Alan DeKok wrote:
Matthew Newton wrote:
However, I looked at and implemented a different solution, which seems to work really well. Taking ideas from inner-tunnels and the SoH check method, I added a virtual_server option to the tls
I've just built a service with this, and there are couple of nice things that I didn't expect (I was just hoping to do some unlang on the certificate data!) First is that you can call the detail module, to get a record of the certificate details easily in a log. Putting detail in post-auth before didn't work as, in post-auth, detail logs the response not the request. I've now got a detail instance in both the tls virtual-server and the post-auth / post-auth reject. This gets me a single log file with two (only) entries for each connection request - both the request AVPs, the certificate data, and the response. Second is that I can call LDAP from the TLS virtual server and check group membership of the connecting device. Without this, you have to call LDAP in the inner tunnel (or outer, if you're doing plain EAP-TLS), and it gets called each time around the challenge-response loop, which hits the LDAP server more than necessary. Here, it just gets called once (see [0] for similar with PEAP/MS-CHAPv2). It's similar to the eap { ok = return } you can do for PEAP, but for EAP-TLS. Cheers, Matthew [0] https://lists.freeradius.org/pipermail/freeradius-devel/2011-June/014492.htm... -- Matthew Newton, Ph.D. <mcn4@le.ac.uk> Systems Architect (UNIX and Networks), Network Services, I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom For IT help contact helpdesk extn. 2253, <ithelp@le.ac.uk>
Matthew Newton wrote:
First is that you can call the detail module, to get a record of the certificate details easily in a log. Putting detail in post-auth before didn't work as, in post-auth, detail logs the response not the request.
Nice!
I've now got a detail instance in both the tls virtual-server and the post-auth / post-auth reject. This gets me a single log file with two (only) entries for each connection request - both the request AVPs, the certificate data, and the response.
That does make it much easier to manage.
Second is that I can call LDAP from the TLS virtual server and check group membership of the connecting device. Without this, you have to call LDAP in the inner tunnel (or outer, if you're doing plain EAP-TLS), and it gets called each time around the challenge-response loop, which hits the LDAP server more than necessary. Here, it just gets called once (see [0] for similar with PEAP/MS-CHAPv2). It's similar to the eap { ok = return } you can do for PEAP, but for EAP-TLS.
That helps, too. My only comment is that the patch is against the v2.1.x branch. Major new functionality needs to go into the "master" branch. Alan DeKok.
Matthew Newton wrote:
I've just built a service with this, and there are couple of nice things that I didn't expect (I was just hoping to do some unlang on the certificate data!)
OK. After some minor persuasion, I've committed it to the "master" branch. Let me know if there are any issues. Can you update the sites-available/check-eap-tls file with more examples? like a detail file && LDAP checks, as you said in your email. That would help document some neat new features. Alan DeKok.
Hi, On Wed, Feb 08, 2012 at 11:47:57AM +0100, Alan DeKok wrote:
My only comment is that the patch is against the v2.1.x branch. Major new functionality needs to go into the "master" branch.
That's fine - I thought it was probably a bit on the edge as to whether it would be suitable for v2.1.x (the whole lot is disabled by one big 'if', so without the virtual-server option, it's all a noop). I'm running 2.1.x for production, hence doing it for that. Although I'm wondering now if I might just jump to master for some of the servers. More testing, and anyway, what can go wrong? ;-) On Wed, Feb 08, 2012 at 12:00:40PM +0100, Alan DeKok wrote:
Matthew Newton wrote:
I've just built a service with this, and there are couple of nice things that I didn't expect (I was just hoping to do some unlang on the certificate data!)
OK. After some minor persuasion, I've committed it to the "master" branch.
Let me know if there are any issues.
Thanks! - I'll check it and see.
Can you update the sites-available/check-eap-tls file with more examples? like a detail file && LDAP checks, as you said in your email. That would help document some neat new features.
Will do. Cheers, Matthew -- Matthew Newton, Ph.D. <mcn4@le.ac.uk> Systems Architect (UNIX and Networks), Network Services, I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom For IT help contact helpdesk extn. 2253, <ithelp@le.ac.uk>
Hi, On Wed, Feb 08, 2012 at 12:00:40PM +0100, Alan DeKok wrote:
Matthew Newton wrote:
I've just built a service with this, and there are couple of nice things that I didn't expect (I was just hoping to do some unlang on the certificate data!)
Let me know if there are any issues.
Still to do - need to get master compiled & running somewhere to test, rather than just compiled :-) (I'm very tempted to just chuck it in on a live server, just to give it a beating. Hey, what can go wrong?! ;-) Maybe a job for Friday afternoon!)
Can you update the sites-available/check-eap-tls file with more examples? like a detail file && LDAP checks, as you said in your email. That would help document some neat new features.
Updated at: https://github.com/mcnewton/freeradius-server/commit/66bf8d5663a2874ce4bafcb... I added 'files' and mentioned sql (which I haven't used myself, but guess it should fit neatly, too). Couldn't think of much else that might want to be called from there (apart from maybe linelog, but that's just as effective in post-auth). Hope it's OK? Cheers, Matthew -- Matthew Newton, Ph.D. <mcn4@le.ac.uk> Systems Architect (UNIX and Networks), Network Services, I.T. Services, University of Leicester, Leicester LE1 7RH, United Kingdom For IT help contact helpdesk extn. 2253, <ithelp@le.ac.uk>
On 02/10/2012 01:02 AM, Matthew Newton wrote:
Still to do - need to get master compiled& running somewhere to test, rather than just compiled :-) (I'm very tempted to just chuck it in on a live server, just to give it a beating. Hey, what can go wrong?! ;-) Maybe a job for Friday afternoon!)
Hehe, I love those Friday afternoon jobs. "I'm bored, time to convert our entire XYZ infrastructure to ABC." FWIW I've had a copy of "master" running on our local testing server, getting nagios traffic for PAP/macauth, MSCHAP, EAP, proxying and SQL, and it's been pretty stable.
Matthew Newton wrote:
Updated at:
https://github.com/mcnewton/freeradius-server/commit/66bf8d5663a2874ce4bafcb...
Pulled, thanks.
I added 'files' and mentioned sql (which I haven't used myself, but guess it should fit neatly, too). Couldn't think of much else that might want to be called from there (apart from maybe linelog, but that's just as effective in post-auth).
Hope it's OK?
Looks good. Alan DeKok.
participants (3)
-
Alan DeKok -
Matthew Newton -
Phil Mayers