From 40c942665e2a6e73653062386b61f09373477e3d Mon Sep 17 00:00:00 2001 From: jmatthew Date: Thu, 13 Oct 2022 04:55:33 +0000 Subject: [PATCH] Add client certificate authentication and optional SASL EXTERNAL bind, which allows the client to bind as the subject of the certificate in cases where the directory doesn't implicitly do that. The client certificate is configured with 'certfile' and 'keyfile' directives, and SASL EXTERNAL bind is configured with the 'bindext' directive. ok tb@ --- usr.sbin/ypldap/aldap.c | 38 +++++++++++++++++- usr.sbin/ypldap/aldap.h | 6 ++- usr.sbin/ypldap/ldapclient.c | 8 +++- usr.sbin/ypldap/parse.y | 75 ++++++++++++++++++++++++++++++++++- usr.sbin/ypldap/ypldap.conf.5 | 18 ++++++++- usr.sbin/ypldap/ypldap.h | 4 +- 6 files changed, 140 insertions(+), 9 deletions(-) diff --git a/usr.sbin/ypldap/aldap.c b/usr.sbin/ypldap/aldap.c index c72547af96b..4efedbeffdb 100644 --- a/usr.sbin/ypldap/aldap.c +++ b/usr.sbin/ypldap/aldap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aldap.c,v 1.48 2022/03/31 09:06:55 martijn Exp $ */ +/* $OpenBSD: aldap.c,v 1.49 2022/10/13 04:55:33 jmatthew Exp $ */ /* * Copyright (c) 2008 Alexander Schrijver @@ -219,6 +219,42 @@ fail: return (-1); } +int +aldap_bind_sasl_external(struct aldap *ldap, char *bindid) +{ + struct ber_element *root = NULL, *elm; + + if ((root = ober_add_sequence(NULL)) == NULL) + goto fail; + + elm = ober_printf_elements(root, "d{tds{ts", ++ldap->msgid, + BER_CLASS_APP, LDAP_REQ_BIND, VERSION, "", + BER_CLASS_CONTEXT, LDAP_AUTH_SASL, LDAP_SASL_MECH_EXTERNAL); + if (elm == NULL) + goto fail; + + if (bindid == NULL) + elm = ober_add_null(elm); + else + elm = ober_add_string(elm, bindid); + + if (elm == NULL) + goto fail; + + LDAP_DEBUG("aldap_bind_sasl_external", root); + + if (aldap_send(ldap, root) == -1) { + root = NULL; + goto fail; + } + return (ldap->msgid); +fail: + ober_free_elements(root); + + ldap->err = ALDAP_ERR_OPERATION_FAILED; + return (-1); +} + int aldap_unbind(struct aldap *ldap) { diff --git a/usr.sbin/ypldap/aldap.h b/usr.sbin/ypldap/aldap.h index c6a09e8ddad..5e84869441e 100644 --- a/usr.sbin/ypldap/aldap.h +++ b/usr.sbin/ypldap/aldap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: aldap.h,v 1.14 2019/05/11 17:46:02 rob Exp $ */ +/* $OpenBSD: aldap.h,v 1.15 2022/10/13 04:55:33 jmatthew Exp $ */ /* * Copyright (c) 2008 Alexander Schrijver @@ -32,6 +32,8 @@ #define LDAP_PAGED_OID "1.2.840.113556.1.4.319" #define LDAP_STARTTLS_OID "1.3.6.1.4.1.1466.20037" +#define LDAP_SASL_MECH_EXTERNAL "EXTERNAL" + struct aldap { #define ALDAP_ERR_SUCCESS 0 #define ALDAP_ERR_PARSER_ERROR 1 @@ -137,6 +139,7 @@ enum deref_aliases { enum authentication_choice { LDAP_AUTH_SIMPLE = 0, + LDAP_AUTH_SASL = 3, }; enum scope { @@ -222,6 +225,7 @@ void aldap_freemsg(struct aldap_message *); int aldap_req_starttls(struct aldap *); int aldap_bind(struct aldap *, char *, char *); +int aldap_bind_sasl_external(struct aldap *, char *); int aldap_unbind(struct aldap *); int aldap_search(struct aldap *, char *, enum scope, char *, char **, int, int, int, struct aldap_page_control *); int aldap_get_errno(struct aldap *, const char **); diff --git a/usr.sbin/ypldap/ldapclient.c b/usr.sbin/ypldap/ldapclient.c index ea1915d687f..f9e089aeb80 100644 --- a/usr.sbin/ypldap/ldapclient.c +++ b/usr.sbin/ypldap/ldapclient.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ldapclient.c,v 1.45 2022/08/22 10:10:59 jmatthew Exp $ */ +/* $OpenBSD: ldapclient.c,v 1.46 2022/10/13 04:55:33 jmatthew Exp $ */ /* * Copyright (c) 2008 Alexander Schrijver @@ -635,7 +635,11 @@ client_try_idm(struct env *env, struct idm *idm) int rc; where = "binding"; - if (aldap_bind(al, idm->idm_binddn, idm->idm_bindcred) == -1) + if (idm->idm_bindext != 0) + rc = aldap_bind_sasl_external(al, idm->idm_bindextid); + else + rc = aldap_bind(al, idm->idm_binddn, idm->idm_bindcred); + if (rc == -1) goto bad; where = "parsing"; diff --git a/usr.sbin/ypldap/parse.y b/usr.sbin/ypldap/parse.y index 9bf8b4127d3..8770a6ca066 100644 --- a/usr.sbin/ypldap/parse.y +++ b/usr.sbin/ypldap/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.35 2022/08/19 03:50:32 jmatthew Exp $ */ +/* $OpenBSD: parse.y,v 1.36 2022/10/13 04:55:33 jmatthew Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -108,7 +108,7 @@ typedef struct { %token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL %token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP %token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS LDAPS TLS CAFILE -%token BIND LOCAL PORTMAP +%token BIND LOCAL PORTMAP BINDEXT CERTFILE KEYFILE %token STRING %token NUMBER %type opcode attribute @@ -213,6 +213,11 @@ attribute : NAME { $$ = 0; } ; diropt : BINDDN STRING { + if (idm->idm_bindext != 0) { + yyerror("can't specify multiple bind types"); + free($2); + YYERROR; + } idm->idm_flags |= F_NEEDAUTH; if (strlcpy(idm->idm_binddn, $2, sizeof(idm->idm_binddn)) >= @@ -224,6 +229,11 @@ diropt : BINDDN STRING { free($2); } | BINDCRED STRING { + if (idm->idm_bindext != 0) { + yyerror("can't specify multiple bind types"); + free($2); + YYERROR; + } idm->idm_flags |= F_NEEDAUTH; if (strlcpy(idm->idm_bindcred, $2, sizeof(idm->idm_bindcred)) >= @@ -234,6 +244,64 @@ diropt : BINDDN STRING { } free($2); } + | BINDEXT STRING { + if (idm->idm_flags & F_NEEDAUTH) { + yyerror("can't specify multiple bind types"); + free($2); + YYERROR; + } + idm->idm_flags |= F_NEEDAUTH; + idm->idm_bindext = 1; + if (strlcpy(idm->idm_bindextid, $2, + sizeof(idm->idm_bindextid)) >= + sizeof(idm->idm_bindextid)) { + yyerror("directory bindext truncated"); + free($2); + YYERROR; + } + free($2); + } + | BINDEXT { + if (idm->idm_flags & F_NEEDAUTH) { + yyerror("can't specify multiple bind types"); + YYERROR; + } + idm->idm_flags |= F_NEEDAUTH; + idm->idm_bindext = 1; + idm->idm_bindextid[0] = '\0'; + } + | CERTFILE STRING { + if (idm->idm_tls_config == NULL) { + yyerror("can't set cert file without tls" + " enabled"); + free($2); + YYERROR; + } + if (tls_config_set_cert_file(idm->idm_tls_config, $2) + == -1) { + yyerror("tls set cert file failed: %s", + tls_config_error( + idm->idm_tls_config)); + free($2); + YYERROR; + } + } + | KEYFILE STRING { + if (idm->idm_tls_config == NULL) { + yyerror("can't set key file without tls" + " enabled"); + free($2); + YYERROR; + } + if (tls_config_set_key_file(idm->idm_tls_config, $2) + == -1) { + yyerror("tls set key file failed: %s", + tls_config_error( + idm->idm_tls_config)); + free($2); + YYERROR; + } + } | BASEDN STRING { if (strlcpy(idm->idm_basedn, $2, sizeof(idm->idm_basedn)) >= @@ -460,7 +528,9 @@ lookup(char *s) { "bind", BIND }, { "bindcred", BINDCRED }, { "binddn", BINDDN }, + { "bindext", BINDEXT }, { "cafile", CAFILE }, + { "certfile", CERTFILE }, { "change", CHANGE }, { "class", CLASS }, { "directory", DIRECTORY }, @@ -479,6 +549,7 @@ lookup(char *s) { "home", HOME }, { "include", INCLUDE }, { "interval", INTERVAL }, + { "keyfile", KEYFILE }, { "ldaps", LDAPS }, { "list", LIST }, { "local", LOCAL }, diff --git a/usr.sbin/ypldap/ypldap.conf.5 b/usr.sbin/ypldap/ypldap.conf.5 index e3fa6235e14..c0b7a2b2cd5 100644 --- a/usr.sbin/ypldap/ypldap.conf.5 +++ b/usr.sbin/ypldap/ypldap.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ypldap.conf.5,v 1.27 2022/08/22 07:07:45 jmatthew Exp $ +.\" $OpenBSD: ypldap.conf.5,v 1.28 2022/10/13 04:55:33 jmatthew Exp $ .\" .\" Copyright (c) 2008 Pierre-Yves Ritschard .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: August 22 2022 $ +.Dd $Mdocdate: October 13 2022 $ .Dt YPLDAP.CONF 5 .Os .Sh NAME @@ -144,6 +144,9 @@ or attribute to the LDAP attribute name supplied. .It Ic basedn Ar string Use the supplied search base as starting point for the directory search. +.It Ic certfile Ar string +Use the specified client certificate when connecting to the directory. +The file must contain a PEM encoded certificate. .It Ic groupdn Ar string Use the supplied search base as starting point for the directory search for groups. @@ -152,12 +155,23 @@ If not supplied, the basedn value will be used. Use the supplied credentials for simple authentication against the directory. .It Ic binddn Ar string Use the supplied Distinguished Name to bind to the directory. +.It Ic bindext Oo Ar string Oc +Bind to the directory using SASL EXTERNAL, optionally using a supplied identity +string. +When using a TLS client certificate, this allows the client to bind as the +subject of the certificate. +If an identity string is supplied, usually in the form of a distinguished name +prefixed with "dn:", the directory will only allow the bind to succeed if it +matches the subject of the certificate. .It Ic fixed attribute Ar attribute string Do not retrieve the specified attribute from LDAP but instead set it unconditionally to the supplied value for every entry. .It Ic group filter Ar string Use the supplied LDAP filter to retrieve group entries. +.It Ic keyfile Ar string +Use the specified private key when connecting to the directory. +The file must contain a PEM encoded key. .It Xo .Ic list Ar name Ic maps to Ar string .Xc diff --git a/usr.sbin/ypldap/ypldap.h b/usr.sbin/ypldap/ypldap.h index 957befc3ebb..1f9a16d814a 100644 --- a/usr.sbin/ypldap/ypldap.h +++ b/usr.sbin/ypldap/ypldap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ypldap.h,v 1.22 2022/08/19 03:50:32 jmatthew Exp $ */ +/* $OpenBSD: ypldap.h,v 1.23 2022/10/13 04:55:33 jmatthew Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard @@ -101,7 +101,9 @@ struct idm { u_int32_t idm_list; struct ypldap_addr_list idm_addr; in_port_t idm_port; + int idm_bindext; char idm_binddn[LINE_WIDTH]; + char idm_bindextid[LINE_WIDTH]; char idm_bindcred[LINE_WIDTH]; char idm_basedn[LINE_WIDTH]; char idm_groupdn[LINE_WIDTH]; -- 2.20.1