diff --git a/.gitignore b/.gitignore index 8e166fc..de9c6af 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,6 @@ tests/t_fticks tests/t_rewrite tests/t_rewrite_config tests/t_resizeattr +tests/t_verify_cert tests/*.log tests/*.trs diff --git a/ChangeLog b/ChangeLog index 90332d4..68dc918 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,20 +1,31 @@ -chanes since 1.8.1 +unreleased chanes New features: - Accept multiple source* configs for IPv4/v6 - Specify source per server - - Bug fixes: - - Fix wrong config-unhexing if %25 (%) occurs + - User configurable cipher-list and ciphersuites + - User configurable TLS versions + - Config option for DH-file + - Add rID and otherName options to certifcateAttributeCheck + - Allow multiple matchCertificateAttribute Misc: - Move radsecproxy manpage to section 8 +2020-08-06 1.8.2 + Bug fixes: + - Fix wrong config-unhexing if %25 (%) occurs + - Fix compatibility with GCC 10 (#63) + - Fix spelling in manpage + - Fix modifyVendorAttribute not applied (#62) + - Fix unncessary status-server when in minimal mode (#61) + 2019-10-01 1.8.1 Bug fixes: - Handle Tunnel-Password attribute correctly - Fix BSD platform issues - Fix spelling in log messages and manpages - Fix compile issues for unit tests + - Don't hardcode location of config files 2019-07-04 1.8.0 New features: diff --git a/README b/README index 33346cf..55e6722 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -This is radsecproxy 1.8.0 +This is radsecproxy 1.8.2 radsecproxy is a generic RADIUS proxy that supports both UDP and TLS (RadSec) RADIUS transports. There is also experimental support for diff --git a/configure.ac b/configure.ac index b69c1b4..00341fc 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Copyright (c) 2006-2010, UNINETT AS dnl Copyright (c) 2010-2013,2016, NORDUnet A/S dnl See LICENSE for licensing information. -AC_INIT(radsecproxy, 1.8.0, https://radsecproxy.github.io) +AC_INIT(radsecproxy, 1.8.2, https://radsecproxy.github.io) AC_CONFIG_AUX_DIR([build-aux]) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE diff --git a/radmsg.c b/radmsg.c index 6828f0d..5f49237 100644 --- a/radmsg.c +++ b/radmsg.c @@ -296,7 +296,7 @@ struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, int secret_len, uint8_t while (p - buf + 2 <= len) { t = *p++; l = *p++; - if (l < 2) { + if (l < 2 || l > 255) { debug(DBG_WARN, "buf2radmsg: invalid attribute length %d", l); radmsg_free(msg); return NULL; diff --git a/radsecproxy.c b/radsecproxy.c index a4b1211..6a2acdb 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -84,6 +84,7 @@ extern int optind; extern char *optarg; #endif static const struct protodefs *protodefs[RAD_PROTOCOUNT]; +pthread_attr_t pthread_attr; /* minimum required declarations to avoid reordering code */ struct realm *adddynamicrealmserver(struct realm *realm, char *id); @@ -1648,8 +1649,11 @@ void *clientwr(void *arg) { #endif pthread_mutex_unlock(&server->newrq_mutex); - for (i = 0; i < MAX_REQUESTS; i++) { - if (server->clientrdgone) { + if (do_resend || server->lastrcv.tv_sec > laststatsrv.tv_sec) + statusserver_requested = 0; + + for (i = 0; i < MAX_REQUESTS; i++) { + if (server->clientrdgone) { server->state = RSP_SERVER_STATE_FAILING; if (conf->pdef->connecter) pthread_join(clientrdth, NULL); @@ -1680,7 +1684,7 @@ void *clientwr(void *arg) { continue; } - if (rqout->tries > 0 && now.tv_sec - server->lastrcv.tv_sec > conf->retryinterval) + if (rqout->tries > 0 && now.tv_sec - server->lastrcv.tv_sec > conf->retryinterval && !do_resend) statusserver_requested = 1; if (rqout->tries == (*rqout->rq->buf == RAD_Status_Server ? 1 : conf->retrycount + 1)) { debug(DBG_DBG, "clientwr: removing expired packet from queue"); @@ -2170,11 +2174,8 @@ void freeclsrvconf(struct clsrvconf *conf) { free(conf->confsecret); free(conf->secret); free(conf->tls); - free(conf->matchcertattr); - if (conf->certcnregex) - regfree(conf->certcnregex); - if (conf->certuriregex) - regfree(conf->certuriregex); + freegconfmstr(conf->confmatchcertattrs); + freematchcertattr(conf); free(conf->confrewritein); free(conf->confrewriteout); if (conf->rewriteusername) { @@ -2264,7 +2265,7 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) { !mergeconfmstring(&dst->source, &src->source) || !mergeconfstring(&dst->confsecret, &src->confsecret) || !mergeconfstring(&dst->tls, &src->tls) || - !mergeconfstring(&dst->matchcertattr, &src->matchcertattr) || + !mergeconfmstring(&dst->confmatchcertattrs, &src->confmatchcertattrs) || !mergeconfstring(&dst->confrewritein, &src->confrewritein) || !mergeconfstring(&dst->confrewriteout, &src->confrewriteout) || !mergeconfstring(&dst->confrewriteusername, &src->confrewriteusername) || @@ -2305,10 +2306,11 @@ int config_hostaf(const char *desc, int ipv4only, int ipv6only, int *af) { int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { struct clsrvconf *conf, *existing; - char *conftype = NULL, *rewriteinalias = NULL; + char *conftype = NULL, *rewriteinalias = NULL, **matchcertattrs = NULL; long int dupinterval = LONG_MIN, addttl = LONG_MIN; uint8_t ipv4only = 0, ipv6only = 0; struct list_node *entry; + int i; debug(DBG_DBG, "confclient_cb called for %s", block); @@ -2327,7 +2329,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char "secret", CONF_STR_NOESC, &conf->confsecret, #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, - "matchcertificateattribute", CONF_STR, &conf->matchcertattr, + "matchcertificateattribute", CONF_MSTR, &matchcertattrs, "CertificateNameCheck", CONF_BLN, &conf->certnamecheck, #endif "DuplicateInterval", CONF_LINT, &dupinterval, @@ -2364,13 +2366,19 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) if (conf->type == RAD_TLS || conf->type == RAD_DTLS) { - conf->tlsconf = conf->tls - ? tlsgettls(conf->tls, NULL) - : tlsgettls("defaultClient", "default"); - if (!conf->tlsconf) - debugx(1, DBG_ERR, "error in block %s, no tls context defined", block); - if (conf->matchcertattr && !addmatchcertattr(conf)) - debugx(1, DBG_ERR, "error in block %s, invalid MatchCertificateAttributeValue", block); + conf->tlsconf = conf->tls + ? tlsgettls(conf->tls, NULL) + : tlsgettls("defaultClient", "default"); + if (!conf->tlsconf) + debugx(1, DBG_ERR, "error in block %s, no tls context defined", block); + if (matchcertattrs) { + for (i=0; matchcertattrs[i]; i++){ + if (!addmatchcertattr(conf, matchcertattrs[i])) { + debugx(1, DBG_ERR, "error in block %s, invalid MatchCertificateAttributeValue", block); + } + } + freegconfmstr(matchcertattrs); + } } #endif @@ -2447,19 +2455,23 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char } int compileserverconfig(struct clsrvconf *conf, const char *block) { + int i; #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) if (conf->type == RAD_TLS || conf->type == RAD_DTLS) { conf->tlsconf = conf->tls ? tlsgettls(conf->tls, NULL) : tlsgettls("defaultServer", "default"); - if (!conf->tlsconf) { - debug(DBG_ERR, "error in block %s, no tls context defined", block); - return 0; - } - if (conf->matchcertattr && !addmatchcertattr(conf)) { - debug(DBG_ERR, "error in block %s, invalid MatchCertificateAttributeValue", block); - return 0; - } + if (!conf->tlsconf) { + debug(DBG_ERR, "error in block %s, no tls context defined", block); + return 0; + } + if (conf->confmatchcertattrs) { + for (i=0; conf->confmatchcertattrs[i]; i++){ + if (!addmatchcertattr(conf, conf->confmatchcertattrs[i])) { + debugx(1, DBG_ERR, "error in block %s, invalid MatchCertificateAttributeValue", block); + } + } + } } #endif @@ -2529,7 +2541,7 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char "secret", CONF_STR_NOESC, &conf->confsecret, #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, - "MatchCertificateAttribute", CONF_STR, &conf->matchcertattr, + "MatchCertificateAttribute", CONF_STR, &conf->confmatchcertattrs, "CertificateNameCheck", CONF_BLN, &conf->certnamecheck, #endif "addTTL", CONF_LINT, &addttl, @@ -3045,7 +3057,8 @@ int radsecproxy_main(int argc, char **argv) { sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGPIPE); pthread_sigmask(SIG_BLOCK, &sigset, NULL); - pthread_create(&sigth, &pthread_attr, sighandler, NULL); + if (pthread_create(&sigth, &pthread_attr, sighandler, NULL)) + debugx(1, DBG_ERR, "pthread_create failed: sighandler"); for (entry = list_first(srvconfs); entry; entry = list_next(entry)) { srvconf = (struct clsrvconf *)entry->data; diff --git a/radsecproxy.conf.5.in b/radsecproxy.conf.5.in index bf1814d..f346e9c 100644 --- a/radsecproxy.conf.5.in +++ b/radsecproxy.conf.5.in @@ -1,4 +1,4 @@ -.TH radsecproxy.conf 5 2019-07-04 "radsecproxy 1.8.0" "" +.TH radsecproxy.conf 5 2020-08-06 "radsecproxy 1.8.2" "" .SH NAME radsecproxy.conf \- Radsec proxy configuration file @@ -413,13 +413,21 @@ For a TLS/DTLS client, disable the default behaviour of matching CN or SubjectAltName against the specified hostname or IP address. .RE -\fBmatchCertificateAttribute (\fR CN \fB|\fR SubjectAltName:URI \fB|\fR SubjectAltName:DNS \fB) :\fR/\fIregexp\fR/ +\fBMatchCertificateAttribute \fRCN:/\fIregexp\fR/ +.br +\fBMatchCertificateAttribute \fRSubjectAltName:DNS:/\fIregexp\fR/ +.br +\fBMatchCertificateAttribute \fRSubjectAltName:URI:/\fIregexp\fR/ .br \fBMatchCertificateAttribute \fRSubjectAltName:IP:\fIaddress\fR +.br +\fBMatchCertificateAttribute \fRSubjectAltName:rID:\fIoid\fR +.br +\fBMatchCertificateAttribute \fRSubjectAltName:otherName:\fIoid\fR:/\fIregexp\fR/ .RS Perform additional validation of certificate attributes. Currently matching -of CN and SubjectAltName types URI DNS and IP is supported. Note that currently this -option can only be specified once in a client block. +of CN and SubjectAltName types URI, DNS, IP, rID, and otherName is supported. If specified +multiple times, all terms must match for the certificate to be considered valid. .RE .BI "DuplicateInterval " seconds @@ -539,7 +547,7 @@ options above. .BI "DynamicLookupCommand " command .RS -Execude the \fIcommand\fR to dynamically configure a server. The executable file +Execute the \fIcommand\fR to dynamically configure a server. The executable file should be given with full path and will be invoked with the name of the realm as its first and only argument. It should either print a valid \fBserver {...}\fR option on stdout and exit with a code of 0 or print nothing and exit with a @@ -617,9 +625,7 @@ block. The details are not repeated here. Please refer to the definitions in the .br .BR "CertificateNameCheck (" on | off ) .br -\fBmatchCertificateAttribute (\fR CN \fB|\fR SubjectAltName:URI \fB|\fR SubjectAltName:DNS \fB) :\fR/\fIregexp\fR/ -.br -\fBMatchCertificateAttribute \fRSubjectAltName:IP:\fIaddress\fR +\fBMatchCertificateAttribute \fR... .br .BR "AddTTL " 1-255 .br @@ -799,6 +805,43 @@ can be triggered by sending a SIGHUP to the radsecproxy process. This option may be set to zero to disable caching. .RE +.BI "CipherList " ciphers +.RS +Specify the list of accepted \fIciphers\fR. See +.BR openssl-ciphers (1). +.RE + +.BI "CipherSuites " ciphersuites +.RS +Specify the \fIciphersuites\fR to be used for TLS1.3. See +.BR openssl-ciphers (1). +.br +Note this requires openssl 1.1.1 +.RE + +.BR "TlsVersion " ( +.IR version " | " minversion : maxversion " )" +.br +.BR "DtlsVersion " ( +.IR version " | " minversion : maxversion " )" +.RS +Specify the TLS/DTLS protocol \fIversion\fR to be used. +.br +Specify the range of allowed protocol versions between \fIminversion\fR and +\fImaxversion\fR (inclusive). If either is left out, any version up to, or +starting from this version is allowed. E.g. "TLS1_2:" will allow TLSv1.2 or later. +.br +Currently supported values are +.BR SSL3 , TLS1 , TLS1_1 , TLS1_2 , TLS1_3 +for TLS and +.BR DTLS1 , DTLS1_1 +for DTLS. +.RE + +.BI "DhFile " file +.RS +DH parameter \fIfile\fR to use. See \fBopenssl-dhparam\fR(1) + .SH "REWRITE BLOCK" .nf @@ -899,7 +942,7 @@ the given vendor id are removed. .BR "WhitelistMode (" on | off ) .RS Enable whitelist mode. All attributes except those configured with -\fBWhitelistAttrbiute\fR or \fBWhitelistVendorAttribute\fR will be removed. +\fBWhitelistAttribute\fR or \fBWhitelistVendorAttribute\fR will be removed. While whitelist mode is active, \fBRemoveAttribute\fR and \fBRemoveVendorAttribute\fR statements are ignored. .RE diff --git a/radsecproxy.h b/radsecproxy.h index 3082300..5917faa 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -12,6 +12,8 @@ #include "gconfig.h" #include "rewrite.h" +#include + #define DEBUG_LEVEL 2 #define CONFIG_MAIN SYSCONFDIR"/radsecproxy.conf" @@ -149,12 +151,8 @@ struct clsrvconf { uint8_t *secret; int secret_len; char *tls; - char *matchcertattr; - regex_t *certcnregex; - regex_t *certuriregex; - regex_t *certdnsregex; - struct in6_addr certipmatch; - int certipmatchaf; + struct list *matchcertattrs; + char **confmatchcertattrs; char *confrewritein; char *confrewriteout; char *confrewriteusername; @@ -263,7 +261,7 @@ int radsrv(struct request *rq); void replyh(struct server *server, unsigned char *buf); struct addrinfo *resolve_hostport_addrinfo(uint8_t type, char *hostport); uint8_t *radattr2ascii(struct tlv *attr); /* TODO: mv this to radmsg? */ -pthread_attr_t pthread_attr; +extern pthread_attr_t pthread_attr; /* Local Variables: */ /* c-file-style: "stroustrup" */ diff --git a/rewrite.c b/rewrite.c index fa50f95..04dae00 100644 --- a/rewrite.c +++ b/rewrite.c @@ -147,7 +147,7 @@ struct modattr *extractmodvattr(char *nameval) { s = strchr(nameval, ':'); vendor = atoi(nameval); - if (!s || !vendor || !strchr(s,':')) + if (!s || !vendor || !strchr(s+1,':')) return NULL; modvattr = extractmodattr(s+1); if (modvattr) @@ -278,7 +278,7 @@ void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmva freegconfmstr(supvattrs); } - if (rma || rmva || adda || moda || supa) { + if (rma || rmva || adda || moda || modva || supa) { rewrite = malloc(sizeof(struct rewrite)); if (!rewrite) debugx(1, DBG_ERR, "malloc failed"); @@ -499,7 +499,7 @@ int dorewritemodvattr(struct tlv *vendortlv, struct modattr *modvattr) { int dorewritemod(struct radmsg *msg, struct list *modattrs, struct list *modvattrs) { struct list_node *n, *m; uint32_t vendor; - + for (n = list_first(msg->attrs); n; n = list_next(n)) { struct tlv *attr = (struct tlv *)n->data; if (attr->t == RAD_Attr_Vendor_Specific) { diff --git a/tests/Makefile.am b/tests/Makefile.am index af5e1c0..728d734 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,7 +4,7 @@ AUTOMAKE_OPTIONS = foreign LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ $(top_srcdir)/build-aux/tap-driver.sh -check_PROGRAMS = t_fticks t_rewrite t_resizeattr t_rewrite_config +check_PROGRAMS = t_fticks t_rewrite t_resizeattr t_rewrite_config t_verify_cert AM_CFLAGS = -g -Wall -Werror @SSL_CFLAGS@ @TARGET_CFLAGS@ LDADD = $(top_builddir)/librsp.a @SSL_LIBS@ LDFLAGS = @SSL_LDFLAGS@ @TARGET_LDFLAGS@ @LDFLAGS@ diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index bbbe469..80d6bee 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -4,16 +4,26 @@ #include #include #include +#include #include "../rewrite.h" #include "../radmsg.h" #include "../debug.h" +static void printescape(uint8_t *v, uint8_t l) { + int i; + for(i=0; idata, (struct tlv *)m->data)) { - printf("attribute list not as expected\n"); + struct tlv *tlv_exp = (struct tlv *)n->data, *tlv_act = (struct tlv *)m->data; + if (!eqtlv(tlv_exp, tlv_act)) { + printf("attribute list at %d not as expected!\n", i); + printf(" expected type: %d, actual type: %d\n", tlv_exp->t, tlv_act->t); + printf(" expected length: %d, actual length: %d\n", tlv_exp->l, tlv_act->l); + printf(" expected value: "); + printescape(tlv_exp->v, tlv_exp->l); + printf(" actual value: "); + printescape(tlv_act->v, tlv_act->l); + printf("\n"); return 1; } m=list_next(m); + i++; } return 0; } @@ -65,7 +84,7 @@ void _reset_rewrite(struct rewrite *rewrite) { int main (int argc, char *argv[]) { - int testcount = 25; + int testcount = 26; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -643,6 +662,34 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test issue #62 + rewrite 9:102:/^(h323-credit-time).*$/\1=86400/ + */ + { + char *value = "h323-credit-time=1846422"; + char *expect = "h323-credit-time=86400"; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + + mod->t = 102; + mod->vendor = 9; + mod->regex = ®ex; + mod->replacement = "\\1=86400"; + regcomp(mod->regex, "^(h323-credit-time).*$", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modvattrs, mod); + list_push(origattrs, makevendortlv(9,maketlv(102,strlen(value), value))); + list_push(expectedattrs, makevendortlv(9,maketlv(102,strlen(expect), expect))); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - issue #62\n", testcount++); + + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + list_destroy(origattrs); list_destroy(expectedattrs); list_destroy(rewrite.addattrs); diff --git a/tests/t_rewrite_config.c b/tests/t_rewrite_config.c index 2e0ac89..129bbbd 100644 --- a/tests/t_rewrite_config.c +++ b/tests/t_rewrite_config.c @@ -15,40 +15,73 @@ main (int argc, char *argv[]) struct rewrite *result; char *rewritename = "rewrite"; char **addattrs; - int numtests = 1, i; + int numtests = 2, i; struct tlv *tlv, *expected; uint8_t expectedvalue[] = {'1',0,0,'1','A','%','4','1'}; printf("1..%d\n", numtests); numtests = 1; - addattrs = malloc(2); - addattrs[0] = stringcopy("1:'1%00%001%41%2541", 0); - addattrs[1] = NULL; + { + addattrs = malloc(2); + addattrs[0] = stringcopy("1:'1%00%001%41%2541", 0); + addattrs[1] = NULL; - expected = maketlv(1,8,expectedvalue); + expected = maketlv(1,8,expectedvalue); - addrewrite(rewritename, 0, NULL, NULL, addattrs, - NULL, NULL, NULL, NULL, NULL); + addrewrite(rewritename, 0, NULL, NULL, addattrs, + NULL, NULL, NULL, NULL, NULL); - result = getrewrite(rewritename, NULL); + result = getrewrite(rewritename, NULL); - if (result->addattrs->first) { - tlv = (struct tlv *)result->addattrs->first->data; - if (!eqtlv(tlv, expected)) { - printf ("tlv value was: 0x"); - for (i = 0; i < tlv->l; i++) { - printf ("%x", *((tlv->v)+i)); + if (result->addattrs->first) { + tlv = (struct tlv *)result->addattrs->first->data; + if (!eqtlv(tlv, expected)) { + printf ("tlv value was: 0x"); + for (i = 0; i < tlv->l; i++) { + printf ("%x", *((tlv->v)+i)); + } + printf ("\n"); + printf ("not "); } - printf ("\n"); - printf ("not "); + printf("ok %d - rewrite config\n", numtests++); + } else { + printf("not ok %d - rewrite config\n", numtests++); } - printf("ok %d - rewrite config\n", numtests++); - } else { - printf("not ok %d - rewrite ocnfig\n", numtests++); + + freetlv(expected); } - freetlv(expected); + /* test issue #62 */ + { + char *expectreplace = "\\1=86400"; + char **modvattrs = malloc(2); + rewritename= "issue62"; + + modvattrs[0] = stringcopy("9:102:/^(h323-credit-time).*$/\\1=86400/",0); + modvattrs[1] = NULL; + + addrewrite(rewritename, 0, NULL, NULL, NULL, NULL, NULL, modvattrs, NULL, NULL); + result = getrewrite(rewritename, NULL); + + if (result && result->modvattrs && result->modvattrs->first) { + struct modattr *mod = (struct modattr *)result->modvattrs->first->data; + if (regexec(mod->regex,"h323-credit-time=1846422",0,NULL,0)) { + printf("not "); + } + if (strcmp(mod->replacement, expectreplace)) { + printf("not "); + } + else if (mod->t != 102 || mod->vendor != 9) { + printf("not "); + } + printf("ok %d - rewrite config issue #62\n", numtests++); + } else { + printf("not ok %d - rewrite config issue #62\n", numtests++); + } + + free(modvattrs); + } return 0; } diff --git a/tests/t_verify_cert.c b/tests/t_verify_cert.c new file mode 100644 index 0000000..ca0d47e --- /dev/null +++ b/tests/t_verify_cert.c @@ -0,0 +1,577 @@ +/* Copyright (C) 2020, SWITCH */ +/* See LICENSE for licensing information. */ + +#include +#include +#include +#include +#include "../radsecproxy.h" +#include "../debug.h" +#include "../hostport.h" +#include "../util.h" + +X509 *getcert(char *pem) { + X509* certX509; + BIO* certBio; + + certBio = BIO_new(BIO_s_mem()); + BIO_write(certBio, pem , strlen(pem)); + certX509 = PEM_read_bio_X509(certBio, NULL, NULL, NULL); + + BIO_free(certBio); + + return certX509; +} + +int numtests = 0; +void ok(int expect, int result, char *descr) { + if (result == expect) { + printf("ok %d - %s\n", ++numtests, descr); + } else { + printf("not ok %d - %s\n", ++numtests, descr); + } +} + +int +main (int argc, char *argv[]) +{ + struct clsrvconf conf; + X509 + /* /CN=test */ + *certsimple = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHAMIGMAgkAx2VNeC1d5FswCQYHKoZIzj0EATAPMQ0wCwYDVQQDDAR0ZXN0MB4X\n\ +DTIwMDkyODE0MTEzMloXDTIwMTAwODE0MTEzMlowDzENMAsGA1UEAwwEdGVzdDAy\n\ +MBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQMNcK0IZozUpupFkD/dWBC37qI\n\ +QW4wCQYHKoZIzj0EAQMkADAhAg8Ajl0dHSkadggaqZiD72ACDjWHqYhaIAWTstBv\n\ +g/Q5\n\ +-----END CERTIFICATE-----"), + + /* /CN=other */ + *certsimpleother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHDMIGOAgkAwf1w/+YshIwwCQYHKoZIzj0EATAQMQ4wDAYDVQQDDAVvdGhlcjAe\n\ +Fw0yMDA5MjkwNTE1MjlaFw0yMDEwMDkwNTE1MjlaMBAxDjAMBgNVBAMMBW90aGVy\n\ +MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEnGezNfbihAw1wrQhmjNSm6kWQP91YELf\n\ +uohBbjAJBgcqhkjOPQQBAyUAMCICDwDD9T+qjNHU461al3c11gIPAMZbk5wkhd6C\n\ +ybOsj/PY\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, SAN DNS:test.local */ +*certsandns = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHrMIG3oAMCAQICFGNCMLUfhveEcLQmEnX2DqjwFZpGMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNjA4NTRaFw0yMDEwMDkxNjA4NTRaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxkwFzAVBgNVHREEDjAMggp0ZXN0LmxvY2FsMAkG\n\ +ByqGSM49BAEDJAAwIQIPAId8FJW00y8XSFmd2lBvAg5K6WAMIFgjhtwcRFcfQg==\n\ +-----END CERTIFICATE-----"), + +/* /CN=other, SAN DNS:other.local */ +*certsandnsother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHuMIG6oAMCAQICFAiFPNqpXcSIwxS0bfJZs8KDDafVMAkGByqGSM49BAEwEDEO\n\ +MAwGA1UEAwwFb3RoZXIwHhcNMjAwOTI5MTYxMTM2WhcNMjAxMDA5MTYxMTM2WjAQ\n\ +MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ +NcK0IZozUpupFkD/dWBC37qIQW6jGjAYMBYGA1UdEQQPMA2CC290aGVyLmxvY2Fs\n\ +MAkGByqGSM49BAEDJAAwIQIOTrQCgOkGcknZEchJFDgCDwCY84F0R2BnNEba95o9\n\ +NA==\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, SAN IP Address:192.0.2.1 */ +*certsanip = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHlMIGxoAMCAQICFEukd75rE75+qB95Bo7fcb9wXlA9MAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkwNTQ5MjZaFw0yMDEwMDkwNTQ5MjZaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxMwETAPBgNVHREECDAGhwTAAAIBMAkGByqGSM49\n\ +BAEDJAAwIQIPALa7jf16ypPNHJSLiotwAg4DnSeToNmbqlRsvM80Aw==\n\ +-----END CERTIFICATE-----"), + +/* /CN=other, SAN IP Address:192.0.2.2 */ +*certsanipother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHmMIGzoAMCAQICFCYvcOo1Lqc+9JbYqfby1S9rJWufMAkGByqGSM49BAEwEDEO\n\ +MAwGA1UEAwwFb3RoZXIwHhcNMjAwOTI5MTU0OTM2WhcNMjAxMDA5MTU0OTM2WjAQ\n\ +MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ +NcK0IZozUpupFkD/dWBC37qIQW6jEzARMA8GA1UdEQQIMAaHBMAAAgIwCQYHKoZI\n\ +zj0EAQMjADAgAg5trehJeRpM04SJJZ6XnAIOFfzRRnQtm5rnsP+QBe8=\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, SAN IP Address:2001:DB8:0:0:0:0:0:1 */ +*certsanipv6 = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHxMIG9oAMCAQICFGhkABYXfolor1EF6Li3hDQeEVU+MAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNTU1NTZaFw0yMDEwMDkxNTU1NTZaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuox8wHTAbBgNVHREEFDAShxAgAQ24AAAAAAAAAAAA\n\ +AAABMAkGByqGSM49BAEDJAAwIQIPAKsn++FWaDIcpnNBOFTuAg5C7gs7DxaNWgEu\n\ +OrBTXA==\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, SAN DNS:192.0.2.1 */ +*certsanipindns = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHqMIG2oAMCAQICFFUjZGG96kpFI2fu90+jAhWsTr8YMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNTU4NDBaFw0yMDEwMDkxNTU4NDBaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxgwFjAUBgNVHREEDTALggkxOTIuMC4yLjEwCQYH\n\ +KoZIzj0EAQMkADAhAg5BngyplTbRlQ8o/oWWwQIPAL9SfgIaXi/gD6YlQCOU\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, SAN DNS:2001:DB8::1 */ +*certsanipv6indns = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHsMIG4oAMCAQICFFgnXltbOEGcWsS0vCv6Lsj4FhO3MAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDA5MjkxNjAyMDRaFw0yMDEwMDkxNjAyMDRaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxowGDAWBgNVHREEDzANggsyMDAxOmRiODo6MTAJ\n\ +BgcqhkjOPQQBAyQAMCECDlWFhJxpHRgt93ZzN9k7Ag8Ag0YN+dL3MEIo2sqgRWg=\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, DNS:somethinglese, DNS:test.local, IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1 */ +*certcomplex = getcert("-----BEGIN CERTIFICATE-----\n\ +MIIBEjCB3qADAgECAhRgxyW7klgZvTf9isALCvwlVwvRtDAJBgcqhkjOPQQBMA8x\n\ +DTALBgNVBAMMBHRlc3QwHhcNMjAwOTMwMDU1MjIyWhcNMjAxMDEwMDU1MjIyWjAP\n\ +MQ0wCwYDVQQDDAR0ZXN0MDIwEAYHKoZIzj0CAQYFK4EEAAYDHgAEnGezNfbihAw1\n\ +wrQhmjNSm6kWQP91YELfuohBbqNAMD4wPAYDVR0RBDUwM4INc29tZXRoaW5nbGVz\n\ +ZYIKdGVzdC5sb2NhbIcEwAACAYcQIAENuAAAAAAAAAAAAAAAATAJBgcqhkjOPQQB\n\ +AyQAMCECDlTfJfMJElZZgvUdkatdAg8ApiXkPXLXXrV6OMRG9us=\n\ +-----END CERTIFICATE-----"), + +/* /CN=test, DNS:somethinglese, DNS:other.local, IP Address:192.0.2.2, IP Address:2001:DB8:0:0:0:0:0:2 */ +*certcomplexother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIIBFTCB4aADAgECAhR0GSgeV7pqQnbHRgv5y5plz/6+NjAJBgcqhkjOPQQBMBAx\n\ +DjAMBgNVBAMMBW90aGVyMB4XDTIwMDkzMDA1NTI1NVoXDTIwMTAxMDA1NTI1NVow\n\ +EDEOMAwGA1UEAwwFb3RoZXIwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKE\n\ +DDXCtCGaM1KbqRZA/3VgQt+6iEFuo0EwPzA9BgNVHREENjA0gg1zb21ldGhpbmds\n\ +ZXNlggtvdGhlci5sb2NhbIcEwAACAocQIAENuAAAAAAAAAAAAAAAAjAJBgcqhkjO\n\ +PQQBAyQAMCECDwCEaHL6oHT4zwH6jD91YwIOYO3L8cHIzmnGCOJYIQ4=\n\ +-----END CERTIFICATE-----"), + + /* /CN=test, URI:https://test.local/profile#me */ + *certsanuri = getcert("-----BEGIN CERTIFICATE-----\n\ +MIH9MIHKoAMCAQICFHsSOjcYexRKQpNlH1oHV1cxvdgHMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDEwMDYwODU5MzRaFw0yMDEwMTYwODU5MzRaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoywwKjAoBgNVHREEITAfhh1odHRwczovL3Rlc3Qu\n\ +bG9jYWwvcHJvZmlsZSNtZTAJBgcqhkjOPQQBAyMAMCACDniwUmV285CoguiJ6WmW\n\ +Ag5ZWNTJtmNNdKxh0Mahsw==\n\ +-----END CERTIFICATE-----"), + + /* /CN=other, URI:https://other.local/profile#me */ + *certsanuriother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIIBATCBzaADAgECAhQLG7rYpl+8YbPNEtUgw6HRZYIc1DAJBgcqhkjOPQQBMBAx\n\ +DjAMBgNVBAMMBW90aGVyMB4XDTIwMTAwNjA5MDU0OVoXDTIwMTAxNjA5MDU0OVow\n\ +EDEOMAwGA1UEAwwFb3RoZXIwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKE\n\ +DDXCtCGaM1KbqRZA/3VgQt+6iEFuoy0wKzApBgNVHREEIjAghh5odHRwczovL290\n\ +aGVyLmxvY2FsL3Byb2ZpbGUjbWUwCQYHKoZIzj0EAQMkADAhAg8AoOJVnRcp3gyY\n\ +Qe0Vy/UCDijCHK6Y5GkzWD7H008l\n\ +-----END CERTIFICATE-----"), + + /* /CN=test, Registered ID:1.2.3.4 */ + *certsanrid = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHjMIGwoAMCAQICFBKq59XodNaMiLZDZbE7BMFn+GnAMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDEwMDYxNTA1NTBaFw0yMDEwMTYxNTA1NTBaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoxIwEDAOBgNVHREEBzAFiAMqAwQwCQYHKoZIzj0E\n\ +AQMjADAgAg4QFOirxwoC5OYpFArE8gIORG+zCoikzhvY95kBGvg=\n\ +-----END CERTIFICATE-----"), + + /* /CN=other, Registered ID:1.2.3.9 */ + *certsanridother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHmMIGyoAMCAQICFEvhI4VZvPr7cITrckvz6J576uy3MAkGByqGSM49BAEwEDEO\n\ +MAwGA1UEAwwFb3RoZXIwHhcNMjAxMDA2MTUwNzQzWhcNMjAxMDE2MTUwNzQzWjAQ\n\ +MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ +NcK0IZozUpupFkD/dWBC37qIQW6jEjAQMA4GA1UdEQQHMAWIAyoDCTAJBgcqhkjO\n\ +PQQBAyQAMCECDwCJMMBtTsOZNwvy43TlLgIOKtssl/hBDN/JcPbBQgI=\n\ +-----END CERTIFICATE-----"), + + /* /CN=test, otherNAME 1.3.6.1.5.5.7.8.8;UTF8:test.local */ + *certsanothername = getcert("-----BEGIN CERTIFICATE-----\n\ +MIH4MIHFoAMCAQICFHfn1oV2cr4BkkWImdYCJXkSmiKrMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDEwMDYxNTE4NTNaFw0yMDEwMTYxNTE4NTNaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoycwJTAjBgNVHREEHDAaoBgGCCsGAQUFBwgIoAwM\n\ +CnRlc3QubG9jYWwwCQYHKoZIzj0EAQMjADAgAg5picQbJfIM1Ljn7H/26QIOCLcA\n\ +UXfI8XA07aHTgzE=\n\ +-----END CERTIFICATE-----"), + + /* /CN=other, otherNAME 1.3.6.1.5.5.7.8.8;UTF8:other.local */ + *certsanothernameother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIH6MIHGoAMCAQICFEa/hIvgCkqCF6ulCq3Jy3iw6XkwMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDEwMDYxNTIwMDhaFw0yMDEwMTYxNTIwMDhaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuoygwJjAkBgNVHREEHTAboBkGCCsGAQUFBwgIoA0M\n\ +C290aGVyLmxvY2FsMAkGByqGSM49BAEDJAAwIQIOSOJ5OK2xzjrCweD/ImECDwDL\n\ +COiok62ckBQsaUG8AA==\n\ +-----END CERTIFICATE-----"), + + /* /CN=test, DNS:test.local, Registered ID:1.2.3.4 */ + *certmulti = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHxMIG8oAMCAQICFFrDaNQffsxLTERNbN7sXupYziWAMAkGByqGSM49BAEwDzEN\n\ +MAsGA1UEAwwEdGVzdDAeFw0yMDEyMTgwOTQwMDFaFw0yMTAxMTcwOTQwMDFaMA8x\n\ +DTALBgNVBAMMBHRlc3QwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAScZ7M19uKEDDXC\n\ +tCGaM1KbqRZA/3VgQt+6iEFuox4wHDAaBgNVHREEEzARggp0ZXN0LmxvY2FsiAMq\n\ +AwQwCQYHKoZIzj0EAQMlADAiAg8AnsiRL2CH3u0bAX/FOt4CDwC9wGzr0l/PCnxK\n\ +mKlpkQ==\n\ +-----END CERTIFICATE-----"), + + /* /CN=other, DNS:other.local, Registered ID:1.2.3.4 */ + *certmultiother = getcert("-----BEGIN CERTIFICATE-----\n\ +MIHyMIG/oAMCAQICFAke6IO1yAeuwOewT/QfAF9afFo7MAkGByqGSM49BAEwEDEO\n\ +MAwGA1UEAwwFb3RoZXIwHhcNMjAxMjE4MDk0NTI1WhcNMjEwMTE3MDk0NTI1WjAQ\n\ +MQ4wDAYDVQQDDAVvdGhlcjAyMBAGByqGSM49AgEGBSuBBAAGAx4ABJxnszX24oQM\n\ +NcK0IZozUpupFkD/dWBC37qIQW6jHzAdMBsGA1UdEQQUMBKCC290aGVyLmxvY2Fs\n\ +iAMqAwQwCQYHKoZIzj0EAQMjADAgAg521Y8BtyeKAMIY8lcLbwIORNNmcwVIJjGj\n\ +vY/uPjA=\n\ +-----END CERTIFICATE-----"); + + memset(&conf, 0, sizeof(conf)); + conf.hostports = list_create(); + + debug_init("t_verify_cert"); + debug_set_level(5); + + /* test check disabled*/ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 0; + hp.host = "test"; + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1, verifyconfcert(certsimple, &conf), "check disabled"); + + while(list_shift(conf.hostports)); + } + + /* test no check if prefixlen != 255 (CIDR) */ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "0.0.0.0/0"; + hp.prefixlen = 0; + list_push(conf.hostports, &hp); + + ok(1,verifyconfcert(certsimple, &conf),"cidr prefix"); + + while(list_shift(conf.hostports)); + } + + /* test simple match for CN=test */ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "test"; + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,verifyconfcert(certsimple, &conf), "simple cert cn"); + ok(0,verifyconfcert(certsimpleother, &conf), "negative simple cert cn"); + + /* as per RFC 6125 6.4.4: CN MUST NOT be matched if SAN is present */ + ok(0,verifyconfcert(certsandns, &conf), "simple cert cn vs san dns, RFC6125"); + + while(list_shift(conf.hostports)); + } + + /* test literal ip match to SAN IP */ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "192.0.2.1"; + getaddrinfo(hp.host, NULL, NULL, &hp.addrinfo); + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,verifyconfcert(certsanip, &conf),"san ip"); + ok(0,verifyconfcert(certsanipother, &conf),"wrong san ip"); + ok(0,verifyconfcert(certsimple, &conf), "negative san ip"); + ok(1,verifyconfcert(certsanipindns, &conf),"san ip in dns"); + ok(1,verifyconfcert(certcomplex,&conf),"san ip in complex cert"); + + freeaddrinfo(hp.addrinfo); + while(list_shift(conf.hostports)); + } + + /* test literal ipv6 match to SAN IP */ + { + struct hostportres hp; + memset(&hp, 0, sizeof(struct hostportres)); + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "2001:db8::1"; + getaddrinfo(hp.host, NULL, NULL, &hp.addrinfo); + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,verifyconfcert(certsanipv6, &conf),"san ipv6"); + ok(0,verifyconfcert(certsanipother, &conf),"wrong san ipv6"); + ok(0,verifyconfcert(certsimple, &conf),"negative san ipv6"); + ok(1,verifyconfcert(certsanipv6indns, &conf),"san ipv6 in dns"); + ok(1,verifyconfcert(certcomplex,&conf),"san ipv6 in complex cert"); + + freeaddrinfo(hp.addrinfo); + while(list_shift(conf.hostports)); + } + + /* test simple match for SAN DNS:test.local */ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "test.local"; + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,verifyconfcert(certsandns, &conf),"san dns"); + ok(0,verifyconfcert(certsandnsother, &conf),"negative san dns"); + ok(1,verifyconfcert(certcomplex,&conf),"san dns in complex cert"); + ok(0,verifyconfcert(certsimple, &conf),"missing san dns"); + + while(list_shift(conf.hostports)); + } + + /* test multiple hostports san dns(match in second) */ + { + struct hostportres hp1, hp2; + + conf.name = "test"; + conf.certnamecheck = 1; + hp1.host = "test.none"; + hp1.prefixlen = 255; + list_push(conf.hostports, &hp1); + hp2.host = "test"; + hp2.prefixlen = 255; + list_push(conf.hostports, &hp2); + + ok(1,verifyconfcert(certsimple, &conf),"multi hostport cn"); + ok(0,verifyconfcert(certsimpleother, &conf),"negative multi hostport cn"); + + while(list_shift(conf.hostports)); + } + + /* test multiple hostports san dns(match in second) */ + { + struct hostportres hp1, hp2; + + conf.name = "test"; + conf.certnamecheck = 1; + hp1.host = "test.none"; + hp1.prefixlen = 255; + list_push(conf.hostports, &hp1); + hp2.host = "test.local"; + hp2.prefixlen = 255; + list_push(conf.hostports, &hp2); + + ok(1,verifyconfcert(certsandns, &conf),"multi hostport san dns"); + ok(0,verifyconfcert(certsandnsother, &conf),"negative multi hostport san dns"); + ok(1,verifyconfcert(certcomplex,&conf),"multi hostport san dns in complex cert"); + + while(list_shift(conf.hostports)); + } + + /* test explicit CN regex */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "CN:/t..t/"),"explicit cn regex config"); + + ok(1,verifyconfcert(certsimple, &conf),"explicit cn regex"); + ok(0,verifyconfcert(certsimpleother, &conf),"negative explicit cn regex"); + ok(1,verifyconfcert(certsandns, &conf), "explicit cn regex with SAN DNS"); + + freematchcertattr(&conf); + } + + /* test explicit ip match to SAN IP */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:IP:192.0.2.1"),"explicit san ip config"); + + ok(1,verifyconfcert(certsanip, &conf),"explicit san ip"); + ok(0,verifyconfcert(certsanipother, &conf),"wrong explicit san ip"); + ok(0,verifyconfcert(certsimple, &conf), "missing explicit san ip"); + ok(1,verifyconfcert(certcomplex,&conf),"explicit san ip in complex cert"); + + freematchcertattr(&conf); + } + + /* test explicit ipv6 match to SAN IP */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:IP:2001:db8::1"),"explicit san ipv6 config"); + + ok(1,verifyconfcert(certsanipv6, &conf),"explicit san ipv6"); + ok(0,verifyconfcert(certsanipother, &conf),"wrong explicit san ipv6"); + ok(0,verifyconfcert(certsimple, &conf),"missing explicitsan ipv6"); + ok(1,verifyconfcert(certcomplex,&conf),"explicit san ipv6 in complex cert"); + + freematchcertattr(&conf); + } + + /* test explicit SAN DNS regex */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:DNS:/t..t\\.local/"),"explicit san dns regex config"); + + ok(1,verifyconfcert(certsandns, &conf),"explicit san dns"); + ok(0,verifyconfcert(certsandnsother, &conf),"negative explicit san dns"); + ok(0,verifyconfcert(certsimple,&conf),"missing explicit san dns"); + ok(1,verifyconfcert(certcomplex,&conf),"explicit san dns in complex cert"); + + freematchcertattr(&conf); + } + + /* test explicit SAN URI regex */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:URI:/https:\\/\\/test.local\\/profile#me/"),"explicit cn regex config"); + + ok(1,verifyconfcert(certsanuri, &conf),"explicit san uri regex"); + ok(0,verifyconfcert(certsanuriother, &conf),"negative explicit san uri"); + ok(0,verifyconfcert(certsimple, &conf), "missing explicit san uri"); + + freematchcertattr(&conf); + } + + /* test explicit SAN rID */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:rID:1.2.3.4"),"explicit san rid config"); + + ok(1,verifyconfcert(certsanrid, &conf),"explicit san rid"); + ok(0,verifyconfcert(certsanridother, &conf),"negative explicit san rid"); + ok(0,verifyconfcert(certsimple, &conf), "missing explicit san rid"); + + freematchcertattr(&conf); + } + + /* test explicit SAN otherNAME */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "SubjectAltName:otherName:1.3.6.1.5.5.7.8.8:/test.local/"),"explicit san otherName config"); + + ok(1,verifyconfcert(certsanothername, &conf),"explicit san otherName"); + ok(0,verifyconfcert(certsanothernameother, &conf),"negative explicit san otherName"); + ok(0,verifyconfcert(certsimple, &conf), "missing explicit san otherName"); + + freematchcertattr(&conf); + } + + /* test valid config syntax */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(1,addmatchcertattr(&conf, "CN:/t..t"),"test regex config syntax"); + ok(1,verifyconfcert(certsimple, &conf),"test regex config syntax execution"); + + freematchcertattr(&conf); + } + + /* test invalid config syntax */ + { + conf.name = "test"; + conf.certnamecheck = 0; + + ok(0,addmatchcertattr(&conf, "CN:t..t"),"test invalid syntax regex"); + freematchcertattr(&conf); + + ok(0,addmatchcertattr(&conf, "SAN:/t..t/"),"test invalid syntax attribute"); + freematchcertattr(&conf); + + ok(0,addmatchcertattr(&conf, "SubjectAltName:IP:1.2.3"),"test invalid syntax ip"); + freematchcertattr(&conf); + + ok(0,addmatchcertattr(&conf, "SubjectAltName:IP:2001:db8:1"),"test invalid syntax ipv6"); + freematchcertattr(&conf); + + ok(0,addmatchcertattr(&conf, "SubjectAltName:rID:1:2"),"test invalid syntax rID"); + freematchcertattr(&conf); + } + + /* test explicit & implicit combined */ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 1; + hp.host = "test.local"; + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,addmatchcertattr(&conf, "CN:/t..t"),"combined config"); + + ok(1,verifyconfcert(certsandns, &conf),"combined san dns"); + ok(0,verifyconfcert(certsandnsother, &conf),"negative combined san dns"); + ok(1,verifyconfcert(certcomplex,&conf),"combined san dns in complex cert"); + ok(0,verifyconfcert(certsimple, &conf),"combined missing san dns"); + + while(list_shift(conf.hostports)); + freematchcertattr(&conf); + } + + /* test multiple explicit checks*/ + { + struct hostportres hp; + + conf.name = "test"; + conf.certnamecheck = 0; + hp.host = "test.local"; + hp.prefixlen = 255; + list_push(conf.hostports, &hp); + + ok(1,addmatchcertattr(&conf, "SubjectAltName:DNS:/test\\.local/"),"multiple check 1"); + ok(1,addmatchcertattr(&conf, "SubjectAltName:rID:1.2.3.4"),"multiple check 2"); + + ok(0,verifyconfcert(certsandns, &conf),"multiple missing rID"); + ok(0,verifyconfcert(certsanrid, &conf), "multiple missing DNS"); + ok(1,verifyconfcert(certmulti, &conf),"multiple SANs"); + ok(0,verifyconfcert(certmultiother, &conf),"multiple negative match"); + ok(0,verifyconfcert(certcomplex, &conf),"multiple missing rID in complex cert"); + ok(0,verifyconfcert(certsimple, &conf),"multiple missing everything"); + + while(list_shift(conf.hostports)); + freematchcertattr(&conf); + } + + printf("1..%d\n", numtests); + list_free(conf.hostports); + X509_free(certsimple); + X509_free(certsimpleother); + X509_free(certsandns); + X509_free(certsandnsother); + X509_free(certsanip); + X509_free(certsanipother); + X509_free(certsanipindns); + X509_free(certsanipv6); + X509_free(certsanipv6indns); + X509_free(certcomplex); + X509_free(certcomplexother); + X509_free(certsanuri); + X509_free(certsanuriother); + X509_free(certsanrid); + X509_free(certsanridother); + X509_free(certsanothername); + X509_free(certsanothernameother); + X509_free(certmulti); + X509_free(certmultiother); + + return 0; +} diff --git a/tlscommon.c b/tlscommon.c index 4522942..48c8db4 100644 --- a/tlscommon.c +++ b/tlscommon.c @@ -39,6 +39,16 @@ static struct hash *tlsconfs = NULL; static unsigned char cookie_secret[COOKIE_SECRET_LENGTH]; static uint8_t cookie_secret_initialized = 0; +struct certattrmatch { + int (*matchfn)(GENERAL_NAME *, struct certattrmatch *); + int type; + char * exact; + regex_t *regex; + ASN1_OBJECT *oid; + struct in6_addr ipaddr; + int af; + char * debugname; +}; /* callbacks for making OpenSSL < 1.1 thread safe */ #if OPENSSL_VERSION_NUMBER < 0x10100000 @@ -348,35 +358,41 @@ static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { case RAD_TLS: #if OPENSSL_VERSION_NUMBER >= 0x10100000 /* TLS_method() was introduced in OpenSSL 1.1.0. */ - ctx = SSL_CTX_new(TLS_method()); + ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_min_proto_version(ctx, conf->tlsminversion); + SSL_CTX_set_max_proto_version(ctx, conf->tlsmaxversion); #else /* No TLS_method(), use SSLv23_method() and disable SSLv2 and SSLv3. */ ctx = SSL_CTX_new(SSLv23_method()); SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); #endif #ifdef DEBUG - SSL_CTX_set_info_callback(ctx, ssl_info_callback); + SSL_CTX_set_info_callback(ctx, ssl_info_callback); #endif - break; + break; #endif #ifdef RADPROT_DTLS case RAD_DTLS: #if OPENSSL_VERSION_NUMBER >= 0x10002000 /* DTLS_method() seems to have been introduced in OpenSSL 1.0.2. */ - ctx = SSL_CTX_new(DTLS_method()); + ctx = SSL_CTX_new(DTLS_method()); +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + SSL_CTX_set_min_proto_version(ctx, conf->dtlsminversion); + SSL_CTX_set_max_proto_version(ctx, conf->dtlsmaxversion); +#endif #else - ctx = SSL_CTX_new(DTLSv1_method()); + ctx = SSL_CTX_new(DTLSv1_method()); #endif #ifdef DEBUG - SSL_CTX_set_info_callback(ctx, ssl_info_callback); + SSL_CTX_set_info_callback(ctx, ssl_info_callback); #endif - SSL_CTX_set_read_ahead(ctx, 1); - break; + 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; + debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name); + return NULL; } #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -394,39 +410,63 @@ static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) { #endif if (conf->certkeypwd) { - SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd); - SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); + 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; + !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 (!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; + if (conf->vpm) { + X509_VERIFY_PARAM_free(conf->vpm); + conf->vpm = NULL; + } + SSL_CTX_free(ctx); + return NULL; + } + + if (conf->cipherlist) { + if (!SSL_CTX_set_cipher_list(ctx, conf->cipherlist)) { + debug(DBG_ERR, "tlscreatectx: Failed to set cipher list in TLS context %s", conf->name); + SSL_CTX_free(ctx); + return NULL; + } } +#if OPENSSL_VERSION_NUMBER >= 0x10101000 + if (conf->ciphersuites) { + if (!SSL_CTX_set_ciphersuites(ctx, conf->ciphersuites)) { + debug(DBG_ERR, "tlscreatectx: Failed to set ciphersuites in TLS context %s", conf->name); + SSL_CTX_free(ctx); + return NULL; + } + } +#endif + if (conf->dhparam) { + if (!SSL_CTX_set_tmp_dh(ctx, conf->dhparam)) { + while ((error = ERR_get_error())) + debug(DBG_WARN, "tlscreatectx: SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_WARN, "tlscreatectx: Failed to set dh params. Can continue, but some ciphers might not be available."); + } + } debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name); return ctx; } @@ -527,177 +567,167 @@ X509 *verifytlscert(SSL *ssl) { 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; +static int certattr_matchrid(GENERAL_NAME *gn, struct certattrmatch *match) { + return OBJ_cmp(gn->d.registeredID, match->oid) == 0 ? 1 : 0; +} - debug(DBG_DBG, "subjectaltnameaddr"); +static int certattr_matchip(GENERAL_NAME *gn, struct certattrmatch *match){ + int l = ASN1_STRING_length(gn->d.iPAddress); + return (((match->af == AF_INET && l == sizeof(struct in_addr)) || (match->af == AF_INET6 && l == sizeof(struct in6_addr))) + && !memcmp(ASN1_STRING_get0_data(gn->d.iPAddress), &match->ipaddr, l)) ? 1 : 0 ; +} - loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); - if (loc < 0) - return r; +static int _general_name_regex_match(char *v, int l, struct certattrmatch *match) { + char *s; + if (l <= 0 ) + return 0; + if (match->exact) { + if (l == strlen(match->exact) && memcmp(v, match->exact, l) == 0) + return 1; + return 0; + } - ex = X509_get_ext(cert, loc); - alt = X509V3_EXT_d2i(ex); - if (!alt) - return r; + s = stringcopy((char *)v, l); + if (!s) { + debug(DBG_ERR, "malloc failed"); + return 0; + } + debug(DBG_DBG, "matchtregex: matching %s", s); + if (regexec(match->regex, s, 0, NULL, 0) == 0) { + free(s); + return 1; + } + free(s); + return 0; +} - 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_get0_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; - } +static int certattr_matchregex(GENERAL_NAME *gn, struct certattrmatch *match) { + return _general_name_regex_match((char *)ASN1_STRING_get0_data(gn->d.ia5), ASN1_STRING_length(gn->d.ia5), match); +} + +static int certattr_matchothername(GENERAL_NAME *gn, struct certattrmatch *match) { + if (OBJ_cmp(gn->d.otherName->type_id, match->oid) != 0) + return 0; + return _general_name_regex_match((char *)ASN1_STRING_get0_data(gn->d.otherName->value->value.octet_string), + ASN1_STRING_length(gn->d.otherName->value->value.octet_string), + match); + +} + +static int certattr_matchcn(X509 *cert, struct certattrmatch *match){ + int loc; + 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); + if (_general_name_regex_match((char *) ASN1_STRING_get0_data(t), ASN1_STRING_length(t), match)) + return 1; } - GENERAL_NAMES_free(alt); - return r; + return 0; } -static int subjectaltnameregexp(X509 *cert, int type, char *exact, regex_t *regex) { - int loc, i, l, n, r = 0; - char *s, *v, *fail = NULL, *tmp; - X509_EXTENSION *ex; - STACK_OF(GENERAL_NAME) *alt; +/* returns + 1 if expected type is present and matches + 0 if expected type is not present + -1 if expected type is present but does not match */ +static int matchsubjaltname(X509 *cert, struct certattrmatch* match) { GENERAL_NAME *gn; + int loc, n,i,r = 0; + char *fail = NULL, *tmp, *s; + STACK_OF(GENERAL_NAME) *alt; - debug(DBG_DBG, "subjectaltnameregexp"); + /*special case: don't search in SAN, but CN field in subject */ + if (match->type == -1) + return certattr_matchcn(cert, match); loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); - if (loc < 0) - return r; + if (loc < 0) + return 0; - ex = X509_get_ext(cert, loc); - alt = X509V3_EXT_d2i(ex); + alt = X509V3_EXT_d2i(X509_get_ext(cert, loc)); if (!alt) - return r; + return 0; 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_get0_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, (uint8_t *) 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; - } - debug(DBG_DBG, "subjectaltnameregex: matching %s", s); - if (regexec(regex, s, 0, NULL, 0)) { + gn = sk_GENERAL_NAME_value(alt, i); + if (gn->type == match->type) { + r = match->matchfn(gn, match); + if (r) + break; + r = -1; + } + /*legacy print non-matching SAN*/ + if (gn->type == GEN_DNS || gn->type == GEN_URI) { + s = stringcopy((char *)ASN1_STRING_get0_data(gn->d.ia5), ASN1_STRING_length(gn->d.ia5)); + if (!s) continue; tmp = fail; if (asprintf(&fail, "%s%s%s", tmp ? tmp : "", tmp ? ", " : "", s) >= 0) free(tmp); else fail = tmp; free(s); - continue; - } - free(s); - } - r = 1; - break; + } } - if (r!=1) - debug(DBG_WARN, "subjectaltnameregex: no matching Subject Alt Name %s found! (%s)", - type == GEN_DNS ? "DNS" : "URI", fail); - GENERAL_NAMES_free(alt); - free(fail); - 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; + if (r<1) + debug(DBG_WARN, "matchsubjaltname: no matching Subject Alt Name found! (%s)", fail); + free(fail); - 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_get0_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; + GENERAL_NAMES_free(alt); + return r; } -/* 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 = 0; - uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */ - struct in6_addr addr; + struct certattrmatch match; + + memset(&match, 0, sizeof(struct certattrmatch)); for (entry = list_first(hostports); entry; entry = list_next(entry)) { + r = 0; 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; + if (inet_pton(AF_INET, hp->host, &match.ipaddr)) + match.af = AF_INET; + else if (inet_pton(AF_INET6, hp->host, &match.ipaddr)) + match.af = AF_INET6; else - type = 0; + match.af = 0; + match.exact = hp->host; - if (type) - r = subjectaltnameaddr(cert, type, &addr); - if (!r) - r = subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL); + if (match.af) { + match.matchfn = &certattr_matchip; + match.type = GEN_IPADD; + r = matchsubjaltname(cert, &match); + } + if (!r) { + match.matchfn = &certattr_matchregex; + match.type = GEN_DNS; + r = matchsubjaltname(cert, &match); + } if (r) { if (r > 0) { - debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host); + debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); return 1; } - debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host); + debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", match.af ? "address" : "host", hp->host); } else { - if (cnregexp(cert, hp->host, NULL)) { + if (certattr_matchcn(cert, &match)) { debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host); return 1; } @@ -709,8 +739,8 @@ int certnamecheck(X509 *cert, struct list *hostports) { int verifyconfcert(X509 *cert, struct clsrvconf *conf) { char *subject; - char addrbuf[INET6_ADDRSTRLEN]; int ok = 1; + struct list_node *entry; subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); debug(DBG_DBG, "verifyconfcert: verify certificate for host %s, subject %s", conf->name, subject); @@ -721,41 +751,70 @@ int verifyconfcert(X509 *cert, struct clsrvconf *conf) { ok = 0; } } - if (conf->certcnregex) { - debug(DBG_DBG, "verifyconfcert: matching CN regex %s", conf->matchcertattr); - if (cnregexp(cert, NULL, conf->certcnregex) < 1) { - debug(DBG_WARN, "verifyconfcert: CN not matching regex for host %s (%s)", conf->name, subject); - ok = 0; - } - } - if (conf->certuriregex) { - debug(DBG_DBG, "verifyconfcert: matching subjectaltname URI regex %s", conf->matchcertattr); - if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) { - debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex for host %s (%s)", conf->name, subject); - ok = 0; - } - } - if (conf->certdnsregex) { - debug(DBG_DBG, "verifyconfcert: matching subjectaltname DNS regex %s", conf->matchcertattr); - if (subjectaltnameregexp(cert, GEN_DNS, NULL, conf->certdnsregex) < 1) { - debug(DBG_WARN, "verifyconfcert: subjectaltname DNS not matching regex for host %s (%s)", conf->name, subject); - ok = 0; - } - } - if (conf->certipmatchaf) { - debug(DBG_DBG, "verifyconfcert: matching subjectaltname IP %s", inet_ntop(conf->certipmatchaf, &conf->certipmatch, addrbuf, INET6_ADDRSTRLEN)); - if (subjectaltnameaddr(cert, conf->certipmatchaf, &conf->certipmatch) < 1) { - debug(DBG_WARN, "verifyconfcert: subjectaltname IP not matching regex for host %s (%s)", conf->name, subject); + + for (entry = list_first(conf->matchcertattrs); entry; entry = list_next(entry)) { + if (matchsubjaltname(cert, (struct certattrmatch *)entry->data) < 1) { + debug(DBG_WARN, "verifyconfcert: %s not matching for host %s (%s)", ((struct certattrmatch *)entry->data)->debugname, conf->name, subject); ok = 0; + } else { + debug(DBG_DBG, "verifyconfcert: %s matching for host %s (%s)", ((struct certattrmatch *)entry->data)->debugname, conf->name, subject); } } + free(subject); return ok; } +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +static int parse_tls_version(const char* version) { + if (!strcasecmp("SSL3", version)) { + return SSL3_VERSION; + } else if (!strcasecmp("TLS1", version)) { + return TLS1_VERSION; + } else if (!strcasecmp("TLS1_1", version)) { + return TLS1_1_VERSION; + } else if (!strcasecmp("TLS1_2", version)) { + return TLS1_2_VERSION; +#if OPENSSL_VERSION_NUMBER >= 0x10101000 + } else if (!strcasecmp("TLS1_3", version)) { + return TLS1_3_VERSION; +#endif + } else if (!strcasecmp("DTLS1", version)) { + return DTLS1_VERSION; + } else if (!strcasecmp("DTLS1_2", version)) { + return DTLS1_2_VERSION; + } else if (!strcasecmp("", version)) { + return 0; + } else { + return -1; + } +} + +static int conf_tls_version(const char *version, int *min, int *max) { + char *ver, *s, *smin, *smax; + ver = stringcopy(version, strlen(version)); + s = strchr(ver, ':'); + if (!s) { + smin = smax = ver; + } else { + *s = '\0'; + smin = ver; + smax = s+1; + } + *min = parse_tls_version(smin); + *max = parse_tls_version(smax); + free(ver); + return *min >=0 && *max >=0 && (*max == 0 || *min <= *max); +} +#endif + int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { struct tls *conf; long int expiry = LONG_MIN; + char *tlsversion = NULL; + char *dtlsversion = NULL; + char *dhfile = NULL; + unsigned long error; debug(DBG_DBG, "conftls_cb called for %s", block); @@ -775,6 +834,11 @@ int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *v "CacheExpiry", CONF_LINT, &expiry, "CRLCheck", CONF_BLN, &conf->crlcheck, "PolicyOID", CONF_MSTR, &conf->policyoids, + "CipherList", CONF_STR, &conf->cipherlist, + "CipherSuites", CONF_STR, &conf->ciphersuites, + "TlsVersion", CONF_STR, &tlsversion, + "DtlsVersion", CONF_STR, &dtlsversion, + "DhFile", CONF_STR, &dhfile, NULL )) { debug(DBG_ERR, "conftls_cb: configuration error in block %s", val); @@ -795,6 +859,47 @@ int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *v } conf->cacheexpiry = expiry; } +#if OPENSSL_VERSION_NUMBER >= 0x10100000 + conf->tlsminversion = TLS1_1_VERSION; + if (tlsversion) { + if(!conf_tls_version(tlsversion, &conf->tlsminversion, &conf->tlsmaxversion)) { + debug(DBG_ERR, "error in block %s, invalid TlsVersion %s", val, tlsversion); + goto errexit; + } + free (tlsversion); + tlsversion = NULL; + } + if (dtlsversion) { + if(!conf_tls_version(dtlsversion, &conf->dtlsminversion, &conf->dtlsmaxversion)) { + debug(DBG_ERR, "error in block %s, invalid DtlsVersion %s", val, dtlsversion); + goto errexit; + } + free (dtlsversion); + dtlsversion = NULL; + } +#else + debug(DBG_ERR, "error in block %s, setting tls/dtls version requires openssl 1.1.0 or later", val); + goto errexit; +#endif + + if (dhfile) { + FILE *dhfp = fopen(dhfile, "r"); + if (dhfp) { + conf->dhparam = PEM_read_DHparams(dhfp, NULL, NULL, NULL); + fclose(dhfp); + if (!conf->dhparam) { + while ((error = ERR_get_error())) + debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL)); + debug(DBG_ERR, "error in block %s: Failed to load DhFile %s.", val, dhfile); + goto errexit; + } + } else { + debug(DBG_ERR, "error in block %s, DhFile: can't open file %s", val, dhfile); + goto errexit; + } + free(dhfile); + dhfile = NULL; + } conf->name = stringcopy(val, 0); if (!conf->name) { @@ -821,58 +926,133 @@ int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *v free(conf->certkeyfile); free(conf->certkeypwd); freegconfmstr(conf->policyoids); + free(tlsversion); + free(dtlsversion); + free(dhfile); + DH_free(conf->dhparam); free(conf); return 0; } -int addmatchcertattr(struct clsrvconf *conf) { - char *v; - regex_t **r; +static regex_t *compileregex(char *regstr) { + regex_t *result; + if (regstr[0] != '/') + return NULL; + regstr++; - if (!strncasecmp(conf->matchcertattr, "SubjectAltName:IP:", 18)) { - if (inet_pton(AF_INET, conf->matchcertattr+18, &conf->certipmatch)) - conf->certipmatchaf = AF_INET; - else if (inet_pton(AF_INET6, conf->matchcertattr+18, &conf->certipmatch)) - conf->certipmatchaf = AF_INET6; - else - return 0; - return 1; + if (regstr[strlen(regstr) - 1] == '/') + regstr[strlen(regstr) - 1] = '\0'; + if (!*regstr) + return NULL; + + result = malloc(sizeof(regex_t)); + if (!result) { + debug(DBG_ERR, "malloc failed"); + return NULL; } + if (regcomp(result, regstr, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { + free(result); + debug(DBG_ERR, "failed to compile regular expression %s", regstr); + return NULL; + } + return result; +} - /* the other cases below use a common regex match */ - 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 if (!strncasecmp(conf->matchcertattr, "SubjectAltName:DNS:/", 20)) { - r = &conf->certdnsregex; - v = conf->matchcertattr + 20; +int addmatchcertattr(struct clsrvconf *conf, const char *match) { + struct certattrmatch *certattrmatch; + char *pos, *colon, *matchcopy; + + if (!conf->matchcertattrs) { + conf->matchcertattrs = list_create(); } - 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; + certattrmatch = malloc(sizeof(struct certattrmatch)); + memset(certattrmatch, 0, sizeof(struct certattrmatch)); + + matchcopy = stringcopy(match,0); + pos = matchcopy; + colon = strchr(pos, ':'); + if (!colon) goto errexit; + + if (strncasecmp(pos, "CN", colon - pos) == 0) { + if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit; + certattrmatch->type = -1; + certattrmatch->matchfn = NULL; /*special case: don't search in SAN, but CN field in subject */ + } + else if (strncasecmp(pos, "SubjectAltName", colon - pos) == 0) { + pos = colon+1; + colon = strchr(pos, ':'); + if (!colon) goto errexit; + + if (strncasecmp(pos, "IP", colon - pos) == 0) { + pos = colon+1; + if (inet_pton(AF_INET, pos, &certattrmatch->ipaddr)) + certattrmatch->af = AF_INET; + else if (inet_pton(AF_INET6, pos, &certattrmatch->ipaddr)) + certattrmatch->af = AF_INET6; + else + goto errexit; + certattrmatch->type = GEN_IPADD; + certattrmatch->matchfn = &certattr_matchip; + } + else if(strncasecmp(pos, "URI", colon - pos) == 0) { + if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit; + certattrmatch->type = GEN_URI; + certattrmatch->matchfn = &certattr_matchregex; + } + else if(strncasecmp(pos, "DNS", colon - pos) == 0) { + if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit; + certattrmatch->type = GEN_DNS; + certattrmatch->matchfn = &certattr_matchregex; + } + else if(strncasecmp(pos, "rID", colon - pos) == 0) { + certattrmatch->oid = OBJ_txt2obj(colon+1, 0); + if (!certattrmatch->oid) goto errexit; + certattrmatch->type = GEN_RID; + certattrmatch->matchfn = &certattr_matchrid; + } + else if(strncasecmp(pos, "otherNAme", colon - pos) == 0){ + pos = colon+1; + colon = strchr(pos, ':'); + if(!colon) goto errexit; + *colon = '\0'; + if(!(certattrmatch->oid = OBJ_txt2obj(pos,0))) goto errexit; + if(!(certattrmatch->regex = compileregex(colon+1))) goto errexit; + certattrmatch->type = GEN_OTHERNAME; + certattrmatch->matchfn = &certattr_matchothername; + } + else goto errexit; + } + else goto errexit; - *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; - } + certattrmatch->debugname = stringcopy(match, 0); + if(!list_push(conf->matchcertattrs, certattrmatch)) goto errexit; + free(matchcopy); return 1; + +errexit: + free(certattrmatch); + free(matchcopy); + return 0; +} + +void freematchcertattr(struct clsrvconf *conf) { + struct list_node *entry; + struct certattrmatch *match; + + if (conf->matchcertattrs) { + for (entry = list_first(conf->matchcertattrs); entry; entry=list_next(entry)) { + match = ((struct certattrmatch*)entry->data); + free(match->debugname); + free(match->exact); + ASN1_OBJECT_free(match->oid); + if(match->regex) + regfree(match->regex); + free(match->regex); + } + list_destroy(conf->matchcertattrs); + conf->matchcertattrs = NULL; + } } int sslaccepttimeout (SSL *ssl, int timeout) { diff --git a/tlscommon.h b/tlscommon.h index 1670136..c376675 100644 --- a/tlscommon.h +++ b/tlscommon.h @@ -18,7 +18,14 @@ struct tls { char *certkeypwd; uint8_t crlcheck; char **policyoids; + char *cipherlist; + char *ciphersuites; uint32_t cacheexpiry; + int tlsminversion; + int tlsmaxversion; + int dtlsminversion; + int dtlsmaxversion; + DH *dhparam; uint32_t tlsexpiry; uint32_t dtlsexpiry; X509_VERIFY_PARAM *vpm; @@ -35,7 +42,8 @@ SSL_CTX *tlsgetctx(uint8_t type, struct tls *t); X509 *verifytlscert(SSL *ssl); int verifyconfcert(X509 *cert, struct clsrvconf *conf); int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); -int addmatchcertattr(struct clsrvconf *conf); +int addmatchcertattr(struct clsrvconf *conf, const char *match); +void freematchcertattr(struct clsrvconf *conf); void tlsreloadcrls(); int sslconnecttimeout(SSL *ssl, int timeout); int sslaccepttimeout (SSL *ssl, int timeout);