]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add the certauth dbmatch module 610/head
authorMatt Rogers <mrogers@redhat.com>
Wed, 15 Mar 2017 23:57:15 +0000 (19:57 -0400)
committerGreg Hudson <ghudson@mit.edu>
Thu, 23 Mar 2017 17:11:49 +0000 (13:11 -0400)
Add and enable the "dbmatch" builtin module.  Add the
pkinit_client_cert_match() and crypto_req_cert_matching_data() helper
functions.  Add dbmatch tests to t_pkinit.py.  Add documentation to
krb5_conf.rst, pkinit.rst, and kadmin_local.rst.

[ghudson@mit.edu: simplified code, edited docs]

ticket: 8562 (new)

doc/admin/admin_commands/kadmin_local.rst
doc/admin/conf_files/krb5_conf.rst
doc/admin/pkinit.rst
src/plugins/preauth/pkinit/pkinit.h
src/plugins/preauth/pkinit/pkinit_crypto.h
src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
src/plugins/preauth/pkinit/pkinit_matching.c
src/plugins/preauth/pkinit/pkinit_srv.c
src/tests/t_pkinit.py

index 50c3b99ea428d1e7eacc8e00ee8ce02c2e99cbbc..9b5ccf4e911a854ed3baa22ab87a5f7b74e99419 100644 (file)
@@ -661,6 +661,13 @@ KDC:
     *principal*.  The *value* is a JSON string representing an array
     of objects, each having optional ``type`` and ``username`` fields.
 
+**pkinit_cert_match**
+    Specifies a matching expression that defines the certificate
+    attributes required for the client certificate used by the
+    principal during PKINIT authentication.  The matching expression
+    is in the same format as those used by the **pkinit_cert_match**
+    option in :ref:`krb5.conf(5)`.  (New in release 1.16.)
+
 This command requires the **modify** privilege.
 
 Alias: **setstr**
index c0e4349c0e1f66be5619219e1689ad3825ddb277..46529990203802e6d25942e563ba04b198d8efdd 100644 (file)
@@ -879,6 +879,11 @@ following built-in modules exist for this interface:
     Extended Key Usage attribute consistent with the
     **pkinit_eku_checking** value for the realm.
 
+**dbmatch**
+    This module authorizes or rejects the certificate according to
+    whether it matches the **pkinit_cert_match** string attribute on
+    the client principal, if that attribute is present.
+
 
 PKINIT options
 --------------
index 460d75d1e2bea6e9638930a6afddb27cb5c61e60..c601c5c9ebbadc222d601ef2a6bdc2a0cfd75ae5 100644 (file)
@@ -223,6 +223,26 @@ time as follows::
 
     kadmin -q 'add_principal +requires_preauth -nokey YOUR_PRINCNAME'
 
+By default, the KDC requires PKINIT client certificates to have the
+standard Extended Key Usage and Subject Alternative Name attributes
+for PKINIT.  Starting in release 1.16, it is possible to authorize
+client certificates based on the subject or other criteria instead of
+the standard PKINIT Subject Alternative Name, by setting the
+**pkinit_cert_match** string attribute on each client principal entry.
+For example::
+
+    kadmin set_string user@REALM pkinit_cert_match "<SUBJECT>CN=user@REALM$"
+
+The **pkinit_cert_match** string attribute follows the syntax used by
+the :ref:`krb5.conf(5)` **pkinit_cert_match** relation.  To allow the
+use of non-PKINIT client certificates, it will also be necessary to
+disable key usage checking using the **pkinit_eku_checking** relation;
+for example::
+
+    [kdcdefaults]
+        pkinit_eku_checking = none
+
+
 
 Configuring the clients
 -----------------------
index 4c488b76bafc227b0df2e10b9d70833d831588b1..2ae3d161b57d4a72fd0405abe4a77d03ed829fb2 100644 (file)
@@ -293,6 +293,13 @@ krb5_error_code pkinit_cert_matching
        pkinit_identity_crypto_context id_cryptoctx,
        krb5_principal princ);
 
+krb5_error_code pkinit_client_cert_match
+       (krb5_context context,
+       pkinit_plg_crypto_context plgctx,
+       pkinit_req_crypto_context reqctx,
+       const char *match_rule,
+       krb5_boolean *matched);
+
 /*
  * Client's list of identities for which it needs PINs or passwords
  */
index a0176acada278cd81702a3c7ee40b3450717490a..9bf6c252bd631043e102b6e6c5fd2488cf5f345c 100644 (file)
@@ -637,4 +637,10 @@ krb5_error_code
 crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx,
                       uint8_t **der_out, size_t *der_len);
 
+krb5_error_code
+crypto_req_cert_matching_data(krb5_context context,
+                             pkinit_plg_crypto_context plgctx,
+                             pkinit_req_crypto_context reqctx,
+                             pkinit_cert_matching_data **md_out);
+
 #endif /* _PKINIT_CRYPTO_H */
index 028ec649d784eb01b54e05f6e0732786a89be940..a1ba9118d02273ac891c85824ba1822752663fa8 100644 (file)
@@ -6124,3 +6124,21 @@ crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx,
     *der_len = len;
     return 0;
 }
+
+/*
+ * Get the certificate matching data from the request certificate.
+ */
+krb5_error_code
+crypto_req_cert_matching_data(krb5_context context,
+                              pkinit_plg_crypto_context plgctx,
+                              pkinit_req_crypto_context reqctx,
+                              pkinit_cert_matching_data **md_out)
+{
+    *md_out = NULL;
+
+    if (reqctx == NULL || reqctx->received_cert == NULL)
+        return ENOENT;
+
+    return get_matching_data(context, plgctx, reqctx, reqctx->received_cert,
+                             md_out);
+}
index 156caf35bdff7ef3936e5032e5be282e3fd4a9c8..aa80f184c9c383dcc86fa1059663315b9ecb232f 100644 (file)
@@ -719,3 +719,40 @@ cleanup:
     crypto_cert_free_matching_data_list(context, matchdata);
     return retval;
 }
+
+krb5_error_code
+pkinit_client_cert_match(krb5_context context,
+                         pkinit_plg_crypto_context plgctx,
+                         pkinit_req_crypto_context reqctx,
+                         const char *match_rule,
+                         krb5_boolean *matched)
+{
+    krb5_error_code ret;
+    pkinit_cert_matching_data *md = NULL;
+    rule_component *rc = NULL;
+    int comp_match = 0;
+    rule_set *rs = NULL;
+
+    *matched = FALSE;
+    ret = parse_rule_set(context, match_rule, &rs);
+    if (ret)
+        goto cleanup;
+
+    ret = crypto_req_cert_matching_data(context, plgctx, reqctx, &md);
+    if (ret)
+        goto cleanup;
+
+    for (rc = rs->crs; rc != NULL; rc = rc->next) {
+        comp_match = component_match(context, rc, md);
+        if ((comp_match && rs->relation == relation_or) ||
+            (!comp_match && rs->relation == relation_and)) {
+            break;
+        }
+    }
+    *matched = comp_match;
+
+cleanup:
+    free_rule_set(context, rs);
+    crypto_cert_free_matching_data(context, md);
+    return ret;
+}
index 731d14eb897a879fcfb5258ae9d64ef99e6dc3f6..91c56d69d83bc612e6d3654758725eb9058daf16 100644 (file)
@@ -1518,6 +1518,56 @@ certauth_pkinit_eku_initvt(krb5_context context, int maj_ver, int min_ver,
     return 0;
 }
 
+/*
+ * Do certificate auth based on a match expression in the pkinit_cert_match
+ * attribute string.  An expression should be in the same form as those used
+ * for the pkinit_cert_match configuration option.
+ */
+static krb5_error_code
+dbmatch_authorize(krb5_context context, krb5_certauth_moddata moddata,
+                  const uint8_t *cert, size_t cert_len,
+                  krb5_const_principal princ, const void *opts,
+                  const krb5_db_entry *db_entry, char ***authinds_out)
+{
+    krb5_error_code ret;
+    const struct certauth_req_opts *req_opts = opts;
+    char *pattern;
+    krb5_boolean matched;
+
+    *authinds_out = NULL;
+
+    /* Fetch the matching pattern.  Pass if it isn't specified. */
+    ret = req_opts->cb->get_string(context, req_opts->rock,
+                                   "pkinit_cert_match", &pattern);
+    if (ret)
+        return ret;
+    if (pattern == NULL)
+        return KRB5_PLUGIN_NO_HANDLE;
+
+    /* Check the certificate against the match expression. */
+    ret = pkinit_client_cert_match(context, req_opts->plgctx->cryptoctx,
+                                   req_opts->reqctx->cryptoctx, pattern,
+                                   &matched);
+    req_opts->cb->free_string(context, req_opts->rock, pattern);
+    if (ret)
+        return ret;
+    return matched ? 0 : KRB5KDC_ERR_CERTIFICATE_MISMATCH;
+}
+
+static krb5_error_code
+certauth_dbmatch_initvt(krb5_context context, int maj_ver, int min_ver,
+                        krb5_plugin_vtable vtable)
+{
+    krb5_certauth_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+    vt = (krb5_certauth_vtable)vtable;
+    vt->name = "dbmatch";
+    vt->authorize = dbmatch_authorize;
+    return 0;
+}
+
 static krb5_error_code
 load_certauth_plugins(krb5_context context, certauth_handle **handle_out)
 {
@@ -1537,6 +1587,11 @@ load_certauth_plugins(krb5_context context, certauth_handle **handle_out)
     if (ret)
         goto cleanup;
 
+    ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH, "dbmatch",
+                             certauth_dbmatch_initvt);
+    if (ret)
+        goto cleanup;
+
     ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CERTAUTH, &modules);
     if (ret)
         goto cleanup;
index d1d49722bb217564233cef65a8c5f9d66a76ddb5..898dafb48a4ba607628d58ff2fa386c6e6cbf85d 100755 (executable)
@@ -292,6 +292,43 @@ realm.run(['./responder', '-X', 'X509_user_identity=%s' % p12_enc_identity,
 realm.klist(realm.user_princ)
 realm.run([kvno, realm.host_princ])
 
+# Match a single rule.
+rule = '<SAN>^user@KRBTEST.COM$'
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
+realm.kinit(realm.user_princ,
+            flags=['-X', 'X509_user_identity=%s' % p12_identity])
+realm.klist(realm.user_princ)
+
+# Match a combined rule (default prefix is &&).
+rule = '<SUBJECT>CN=user$<KU>digitalSignature,keyEncipherment'
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
+realm.kinit(realm.user_princ,
+            flags=['-X', 'X509_user_identity=%s' % p12_identity])
+realm.klist(realm.user_princ)
+
+# Fail an && rule.
+rule = '&&<SUBJECT>O=OTHER.COM<SAN>^user@KRBTEST.COM$'
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
+msg = 'kinit: Certificate mismatch while getting initial credentials'
+realm.kinit(realm.user_princ,
+            flags=['-X', 'X509_user_identity=%s' % p12_identity],
+            expected_code=1, expected_msg=msg)
+
+# Pass an || rule.
+rule = '||<SUBJECT>O=KRBTEST.COM<SAN>^otheruser@KRBTEST.COM$'
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
+realm.kinit(realm.user_princ,
+            flags=['-X', 'X509_user_identity=%s' % p12_identity])
+realm.klist(realm.user_princ)
+
+# Fail an || rule.
+rule = '||<SUBJECT>O=OTHER.COM<SAN>^otheruser@KRBTEST.COM$'
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
+msg = 'kinit: Certificate mismatch while getting initial credentials'
+realm.kinit(realm.user_princ,
+            flags=['-X', 'X509_user_identity=%s' % p12_identity],
+            expected_code=1, expected_msg=msg)
+
 if not have_soft_pkcs11:
     skip_rest('PKINIT PKCS11 tests', 'soft-pkcs11.so not found')