|
|
d738b9 |
From fab1e4f8553dcf8a573c41bb8ea93912a622aae0 Mon Sep 17 00:00:00 2001
|
|
|
d738b9 |
From: Matt Rogers <mrogers@redhat.com>
|
|
|
d738b9 |
Date: Wed, 15 Mar 2017 19:57:15 -0400
|
|
|
d738b9 |
Subject: [PATCH] Add the certauth dbmatch module
|
|
|
d738b9 |
|
|
|
d738b9 |
Add and enable the "dbmatch" builtin module. Add the
|
|
|
d738b9 |
pkinit_client_cert_match() and crypto_req_cert_matching_data() helper
|
|
|
d738b9 |
functions. Add dbmatch tests to t_pkinit.py. Add documentation to
|
|
|
d738b9 |
krb5_conf.rst, pkinit.rst, and kadmin_local.rst.
|
|
|
d738b9 |
|
|
|
d738b9 |
[ghudson@mit.edu: simplified code, edited docs]
|
|
|
d738b9 |
|
|
|
d738b9 |
ticket: 8562 (new)
|
|
|
d738b9 |
(cherry picked from commit 89634ca049e698d7dd2554f5c49bfc499be96188)
|
|
|
d738b9 |
---
|
|
|
d738b9 |
doc/admin/admin_commands/kadmin_local.rst | 7 +++
|
|
|
d738b9 |
doc/admin/conf_files/krb5_conf.rst | 5 ++
|
|
|
d738b9 |
doc/admin/pkinit.rst | 20 +++++++
|
|
|
d738b9 |
src/plugins/preauth/pkinit/pkinit.h | 7 +++
|
|
|
d738b9 |
src/plugins/preauth/pkinit/pkinit_crypto.h | 6 ++
|
|
|
d738b9 |
.../preauth/pkinit/pkinit_crypto_openssl.c | 18 ++++++
|
|
|
d738b9 |
src/plugins/preauth/pkinit/pkinit_matching.c | 37 +++++++++++++
|
|
|
d738b9 |
src/plugins/preauth/pkinit/pkinit_srv.c | 55 +++++++++++++++++++
|
|
|
d738b9 |
src/tests/t_pkinit.py | 37 +++++++++++++
|
|
|
d738b9 |
9 files changed, 192 insertions(+)
|
|
|
d738b9 |
|
|
|
d738b9 |
diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst
|
|
|
d738b9 |
index 0e955faf2..cefe6054b 100644
|
|
|
d738b9 |
--- a/doc/admin/admin_commands/kadmin_local.rst
|
|
|
d738b9 |
+++ b/doc/admin/admin_commands/kadmin_local.rst
|
|
|
d738b9 |
@@ -661,6 +661,13 @@ KDC:
|
|
|
d738b9 |
*principal*. The *value* is a JSON string representing an array
|
|
|
d738b9 |
of objects, each having optional ``type`` and ``username`` fields.
|
|
|
d738b9 |
|
|
|
d738b9 |
+**pkinit_cert_match**
|
|
|
d738b9 |
+ Specifies a matching expression that defines the certificate
|
|
|
d738b9 |
+ attributes required for the client certificate used by the
|
|
|
d738b9 |
+ principal during PKINIT authentication. The matching expression
|
|
|
d738b9 |
+ is in the same format as those used by the **pkinit_cert_match**
|
|
|
d738b9 |
+ option in :ref:`krb5.conf(5)`. (New in release 1.16.)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
This command requires the **modify** privilege.
|
|
|
d738b9 |
|
|
|
d738b9 |
Alias: **setstr**
|
|
|
d738b9 |
diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
|
|
|
d738b9 |
index cc996f11a..d428124c9 100644
|
|
|
d738b9 |
--- a/doc/admin/conf_files/krb5_conf.rst
|
|
|
d738b9 |
+++ b/doc/admin/conf_files/krb5_conf.rst
|
|
|
d738b9 |
@@ -883,6 +883,11 @@ following built-in modules exist for this interface:
|
|
|
d738b9 |
Extended Key Usage attribute consistent with the
|
|
|
d738b9 |
**pkinit_eku_checking** value for the realm.
|
|
|
d738b9 |
|
|
|
d738b9 |
+**dbmatch**
|
|
|
d738b9 |
+ This module authorizes or rejects the certificate according to
|
|
|
d738b9 |
+ whether it matches the **pkinit_cert_match** string attribute on
|
|
|
d738b9 |
+ the client principal, if that attribute is present.
|
|
|
d738b9 |
+
|
|
|
d738b9 |
|
|
|
d738b9 |
PKINIT options
|
|
|
d738b9 |
--------------
|
|
|
d738b9 |
diff --git a/doc/admin/pkinit.rst b/doc/admin/pkinit.rst
|
|
|
d738b9 |
index 460d75d1e..c601c5c9e 100644
|
|
|
d738b9 |
--- a/doc/admin/pkinit.rst
|
|
|
d738b9 |
+++ b/doc/admin/pkinit.rst
|
|
|
d738b9 |
@@ -223,6 +223,26 @@ time as follows::
|
|
|
d738b9 |
|
|
|
d738b9 |
kadmin -q 'add_principal +requires_preauth -nokey YOUR_PRINCNAME'
|
|
|
d738b9 |
|
|
|
d738b9 |
+By default, the KDC requires PKINIT client certificates to have the
|
|
|
d738b9 |
+standard Extended Key Usage and Subject Alternative Name attributes
|
|
|
d738b9 |
+for PKINIT. Starting in release 1.16, it is possible to authorize
|
|
|
d738b9 |
+client certificates based on the subject or other criteria instead of
|
|
|
d738b9 |
+the standard PKINIT Subject Alternative Name, by setting the
|
|
|
d738b9 |
+**pkinit_cert_match** string attribute on each client principal entry.
|
|
|
d738b9 |
+For example::
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ kadmin set_string user@REALM pkinit_cert_match "<SUBJECT>CN=user@REALM$"
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+The **pkinit_cert_match** string attribute follows the syntax used by
|
|
|
d738b9 |
+the :ref:`krb5.conf(5)` **pkinit_cert_match** relation. To allow the
|
|
|
d738b9 |
+use of non-PKINIT client certificates, it will also be necessary to
|
|
|
d738b9 |
+disable key usage checking using the **pkinit_eku_checking** relation;
|
|
|
d738b9 |
+for example::
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ [kdcdefaults]
|
|
|
d738b9 |
+ pkinit_eku_checking = none
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+
|
|
|
d738b9 |
|
|
|
d738b9 |
Configuring the clients
|
|
|
d738b9 |
-----------------------
|
|
|
d738b9 |
diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h
|
|
|
d738b9 |
index a49f3078e..430b3f334 100644
|
|
|
d738b9 |
--- a/src/plugins/preauth/pkinit/pkinit.h
|
|
|
d738b9 |
+++ b/src/plugins/preauth/pkinit/pkinit.h
|
|
|
d738b9 |
@@ -292,6 +292,13 @@ krb5_error_code pkinit_cert_matching
|
|
|
d738b9 |
pkinit_identity_crypto_context id_cryptoctx,
|
|
|
d738b9 |
krb5_principal princ);
|
|
|
d738b9 |
|
|
|
d738b9 |
+krb5_error_code pkinit_client_cert_match
|
|
|
d738b9 |
+ (krb5_context context,
|
|
|
d738b9 |
+ pkinit_plg_crypto_context plgctx,
|
|
|
d738b9 |
+ pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
+ const char *match_rule,
|
|
|
d738b9 |
+ krb5_boolean *matched);
|
|
|
d738b9 |
+
|
|
|
d738b9 |
/*
|
|
|
d738b9 |
* Client's list of identities for which it needs PINs or passwords
|
|
|
d738b9 |
*/
|
|
|
d738b9 |
diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h
|
|
|
d738b9 |
index b6e4e0ac3..149923b1d 100644
|
|
|
d738b9 |
--- a/src/plugins/preauth/pkinit/pkinit_crypto.h
|
|
|
d738b9 |
+++ b/src/plugins/preauth/pkinit/pkinit_crypto.h
|
|
|
d738b9 |
@@ -637,4 +637,10 @@ krb5_error_code
|
|
|
d738b9 |
crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
uint8_t **der_out, size_t *der_len);
|
|
|
d738b9 |
|
|
|
d738b9 |
+krb5_error_code
|
|
|
d738b9 |
+crypto_req_cert_matching_data(krb5_context context,
|
|
|
d738b9 |
+ pkinit_plg_crypto_context plgctx,
|
|
|
d738b9 |
+ pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
+ pkinit_cert_matching_data **md_out);
|
|
|
d738b9 |
+
|
|
|
d738b9 |
#endif /* _PKINIT_CRYPTO_H */
|
|
|
d738b9 |
diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
|
|
|
d738b9 |
index 3949eb9c2..534161b19 100644
|
|
|
d738b9 |
--- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
|
|
|
d738b9 |
+++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
|
|
|
d738b9 |
@@ -6099,3 +6099,21 @@ crypto_encode_der_cert(krb5_context context, pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
*der_len = len;
|
|
|
d738b9 |
return 0;
|
|
|
d738b9 |
}
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+/*
|
|
|
d738b9 |
+ * Get the certificate matching data from the request certificate.
|
|
|
d738b9 |
+ */
|
|
|
d738b9 |
+krb5_error_code
|
|
|
d738b9 |
+crypto_req_cert_matching_data(krb5_context context,
|
|
|
d738b9 |
+ pkinit_plg_crypto_context plgctx,
|
|
|
d738b9 |
+ pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
+ pkinit_cert_matching_data **md_out)
|
|
|
d738b9 |
+{
|
|
|
d738b9 |
+ *md_out = NULL;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ if (reqctx == NULL || reqctx->received_cert == NULL)
|
|
|
d738b9 |
+ return ENOENT;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ return get_matching_data(context, plgctx, reqctx, reqctx->received_cert,
|
|
|
d738b9 |
+ md_out);
|
|
|
d738b9 |
+}
|
|
|
d738b9 |
diff --git a/src/plugins/preauth/pkinit/pkinit_matching.c b/src/plugins/preauth/pkinit/pkinit_matching.c
|
|
|
d738b9 |
index d929fb3c0..c2a4c084d 100644
|
|
|
d738b9 |
--- a/src/plugins/preauth/pkinit/pkinit_matching.c
|
|
|
d738b9 |
+++ b/src/plugins/preauth/pkinit/pkinit_matching.c
|
|
|
d738b9 |
@@ -724,3 +724,40 @@ cleanup:
|
|
|
d738b9 |
crypto_cert_free_matching_data_list(context, matchdata);
|
|
|
d738b9 |
return retval;
|
|
|
d738b9 |
}
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+krb5_error_code
|
|
|
d738b9 |
+pkinit_client_cert_match(krb5_context context,
|
|
|
d738b9 |
+ pkinit_plg_crypto_context plgctx,
|
|
|
d738b9 |
+ pkinit_req_crypto_context reqctx,
|
|
|
d738b9 |
+ const char *match_rule,
|
|
|
d738b9 |
+ krb5_boolean *matched)
|
|
|
d738b9 |
+{
|
|
|
d738b9 |
+ krb5_error_code ret;
|
|
|
d738b9 |
+ pkinit_cert_matching_data *md = NULL;
|
|
|
d738b9 |
+ rule_component *rc = NULL;
|
|
|
d738b9 |
+ int comp_match = 0;
|
|
|
d738b9 |
+ rule_set *rs = NULL;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ *matched = FALSE;
|
|
|
d738b9 |
+ ret = parse_rule_set(context, match_rule, &rs);
|
|
|
d738b9 |
+ if (ret)
|
|
|
d738b9 |
+ goto cleanup;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ ret = crypto_req_cert_matching_data(context, plgctx, reqctx, &md);
|
|
|
d738b9 |
+ if (ret)
|
|
|
d738b9 |
+ goto cleanup;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ for (rc = rs->crs; rc != NULL; rc = rc->next) {
|
|
|
d738b9 |
+ comp_match = component_match(context, rc, md);
|
|
|
d738b9 |
+ if ((comp_match && rs->relation == relation_or) ||
|
|
|
d738b9 |
+ (!comp_match && rs->relation == relation_and)) {
|
|
|
d738b9 |
+ break;
|
|
|
d738b9 |
+ }
|
|
|
d738b9 |
+ }
|
|
|
d738b9 |
+ *matched = comp_match;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+cleanup:
|
|
|
d738b9 |
+ free_rule_set(context, rs);
|
|
|
d738b9 |
+ crypto_cert_free_matching_data(context, md);
|
|
|
d738b9 |
+ return ret;
|
|
|
d738b9 |
+}
|
|
|
d738b9 |
diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c
|
|
|
d738b9 |
index 42ad45fe4..7d86e597e 100644
|
|
|
d738b9 |
--- a/src/plugins/preauth/pkinit/pkinit_srv.c
|
|
|
d738b9 |
+++ b/src/plugins/preauth/pkinit/pkinit_srv.c
|
|
|
d738b9 |
@@ -1537,6 +1537,56 @@ certauth_pkinit_eku_initvt(krb5_context context, int maj_ver, int min_ver,
|
|
|
d738b9 |
return 0;
|
|
|
d738b9 |
}
|
|
|
d738b9 |
|
|
|
d738b9 |
+/*
|
|
|
d738b9 |
+ * Do certificate auth based on a match expression in the pkinit_cert_match
|
|
|
d738b9 |
+ * attribute string. An expression should be in the same form as those used
|
|
|
d738b9 |
+ * for the pkinit_cert_match configuration option.
|
|
|
d738b9 |
+ */
|
|
|
d738b9 |
+static krb5_error_code
|
|
|
d738b9 |
+dbmatch_authorize(krb5_context context, krb5_certauth_moddata moddata,
|
|
|
d738b9 |
+ const uint8_t *cert, size_t cert_len,
|
|
|
d738b9 |
+ krb5_const_principal princ, const void *opts,
|
|
|
d738b9 |
+ const krb5_db_entry *db_entry, char ***authinds_out)
|
|
|
d738b9 |
+{
|
|
|
d738b9 |
+ krb5_error_code ret;
|
|
|
d738b9 |
+ const struct certauth_req_opts *req_opts = opts;
|
|
|
d738b9 |
+ char *pattern;
|
|
|
d738b9 |
+ krb5_boolean matched;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ *authinds_out = NULL;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ /* Fetch the matching pattern. Pass if it isn't specified. */
|
|
|
d738b9 |
+ ret = req_opts->cb->get_string(context, req_opts->rock,
|
|
|
d738b9 |
+ "pkinit_cert_match", &pattern);
|
|
|
d738b9 |
+ if (ret)
|
|
|
d738b9 |
+ return ret;
|
|
|
d738b9 |
+ if (pattern == NULL)
|
|
|
d738b9 |
+ return KRB5_PLUGIN_NO_HANDLE;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ /* Check the certificate against the match expression. */
|
|
|
d738b9 |
+ ret = pkinit_client_cert_match(context, req_opts->plgctx->cryptoctx,
|
|
|
d738b9 |
+ req_opts->reqctx->cryptoctx, pattern,
|
|
|
d738b9 |
+ &matched);
|
|
|
d738b9 |
+ req_opts->cb->free_string(context, req_opts->rock, pattern);
|
|
|
d738b9 |
+ if (ret)
|
|
|
d738b9 |
+ return ret;
|
|
|
d738b9 |
+ return matched ? 0 : KRB5KDC_ERR_CERTIFICATE_MISMATCH;
|
|
|
d738b9 |
+}
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+static krb5_error_code
|
|
|
d738b9 |
+certauth_dbmatch_initvt(krb5_context context, int maj_ver, int min_ver,
|
|
|
d738b9 |
+ krb5_plugin_vtable vtable)
|
|
|
d738b9 |
+{
|
|
|
d738b9 |
+ krb5_certauth_vtable vt;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+ if (maj_ver != 1)
|
|
|
d738b9 |
+ return KRB5_PLUGIN_VER_NOTSUPP;
|
|
|
d738b9 |
+ vt = (krb5_certauth_vtable)vtable;
|
|
|
d738b9 |
+ vt->name = "dbmatch";
|
|
|
d738b9 |
+ vt->authorize = dbmatch_authorize;
|
|
|
d738b9 |
+ return 0;
|
|
|
d738b9 |
+}
|
|
|
d738b9 |
+
|
|
|
d738b9 |
static krb5_error_code
|
|
|
d738b9 |
load_certauth_plugins(krb5_context context, certauth_handle **handle_out)
|
|
|
d738b9 |
{
|
|
|
d738b9 |
@@ -1556,6 +1606,11 @@ load_certauth_plugins(krb5_context context, certauth_handle **handle_out)
|
|
|
d738b9 |
if (ret)
|
|
|
d738b9 |
goto cleanup;
|
|
|
d738b9 |
|
|
|
d738b9 |
+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH, "dbmatch",
|
|
|
d738b9 |
+ certauth_dbmatch_initvt);
|
|
|
d738b9 |
+ if (ret)
|
|
|
d738b9 |
+ goto cleanup;
|
|
|
d738b9 |
+
|
|
|
d738b9 |
ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CERTAUTH, &modules);
|
|
|
d738b9 |
if (ret)
|
|
|
d738b9 |
goto cleanup;
|
|
|
d738b9 |
diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py
|
|
|
d738b9 |
index 64ff2393a..5a0624de7 100755
|
|
|
d738b9 |
--- a/src/tests/t_pkinit.py
|
|
|
d738b9 |
+++ b/src/tests/t_pkinit.py
|
|
|
d738b9 |
@@ -305,6 +305,43 @@ realm.run(['./responder', '-X', 'X509_user_identity=%s' % p12_enc_identity,
|
|
|
d738b9 |
realm.klist(realm.user_princ)
|
|
|
d738b9 |
realm.run([kvno, realm.host_princ])
|
|
|
d738b9 |
|
|
|
d738b9 |
+# Match a single rule.
|
|
|
d738b9 |
+rule = '<SAN>^user@KRBTEST.COM$'
|
|
|
d738b9 |
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
|
|
|
d738b9 |
+realm.kinit(realm.user_princ,
|
|
|
d738b9 |
+ flags=['-X', 'X509_user_identity=%s' % p12_identity])
|
|
|
d738b9 |
+realm.klist(realm.user_princ)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+# Match a combined rule (default prefix is &&).
|
|
|
d738b9 |
+rule = '<SUBJECT>CN=user$<KU>digitalSignature,keyEncipherment'
|
|
|
d738b9 |
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
|
|
|
d738b9 |
+realm.kinit(realm.user_princ,
|
|
|
d738b9 |
+ flags=['-X', 'X509_user_identity=%s' % p12_identity])
|
|
|
d738b9 |
+realm.klist(realm.user_princ)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+# Fail an && rule.
|
|
|
d738b9 |
+rule = '&&<SUBJECT>O=OTHER.COM<SAN>^user@KRBTEST.COM$'
|
|
|
d738b9 |
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
|
|
|
d738b9 |
+msg = 'kinit: Certificate mismatch while getting initial credentials'
|
|
|
d738b9 |
+realm.kinit(realm.user_princ,
|
|
|
d738b9 |
+ flags=['-X', 'X509_user_identity=%s' % p12_identity],
|
|
|
d738b9 |
+ expected_code=1, expected_msg=msg)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+# Pass an || rule.
|
|
|
d738b9 |
+rule = '||<SUBJECT>O=KRBTEST.COM<SAN>^otheruser@KRBTEST.COM$'
|
|
|
d738b9 |
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
|
|
|
d738b9 |
+realm.kinit(realm.user_princ,
|
|
|
d738b9 |
+ flags=['-X', 'X509_user_identity=%s' % p12_identity])
|
|
|
d738b9 |
+realm.klist(realm.user_princ)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
+# Fail an || rule.
|
|
|
d738b9 |
+rule = '||<SUBJECT>O=OTHER.COM<SAN>^otheruser@KRBTEST.COM$'
|
|
|
d738b9 |
+realm.run([kadminl, 'setstr', realm.user_princ, 'pkinit_cert_match', rule])
|
|
|
d738b9 |
+msg = 'kinit: Certificate mismatch while getting initial credentials'
|
|
|
d738b9 |
+realm.kinit(realm.user_princ,
|
|
|
d738b9 |
+ flags=['-X', 'X509_user_identity=%s' % p12_identity],
|
|
|
d738b9 |
+ expected_code=1, expected_msg=msg)
|
|
|
d738b9 |
+
|
|
|
d738b9 |
if not have_soft_pkcs11:
|
|
|
d738b9 |
skip_rest('PKINIT PKCS11 tests', 'soft-pkcs11.so not found')
|
|
|
d738b9 |
|