Add client certificate authentication and optional SASL EXTERNAL bind,
authorjmatthew <jmatthew@openbsd.org>
Thu, 13 Oct 2022 04:55:33 +0000 (04:55 +0000)
committerjmatthew <jmatthew@openbsd.org>
Thu, 13 Oct 2022 04:55:33 +0000 (04:55 +0000)
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
usr.sbin/ypldap/aldap.h
usr.sbin/ypldap/ldapclient.c
usr.sbin/ypldap/parse.y
usr.sbin/ypldap/ypldap.conf.5
usr.sbin/ypldap/ypldap.h

index c72547a..4efedbe 100644 (file)
@@ -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 <aschrijver@openbsd.org>
@@ -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)
 {
index c6a09e8..5e84869 100644 (file)
@@ -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 <aschrijver@openbsd.org>
@@ -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 **);
index ea1915d..f9e089a 100644 (file)
@@ -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 <aschrijver@openbsd.org>
@@ -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";
index 9bf8b41..8770a6c 100644 (file)
@@ -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 <pyr@openbsd.org>
@@ -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 <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.number>      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 },
index e3fa623..c0b7a2b 100644 (file)
@@ -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 <pyr@openbsd.org>
 .\"
@@ -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
index 957befc..1f9a16d 100644 (file)
@@ -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 <pyr@openbsd.org>
@@ -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];