krb5 commit: Support KRB5_CERTAUTH_HWAUTH_PASS in certauth
Greg Hudson
ghudson at mit.edu
Thu Oct 28 15:59:43 EDT 2021
https://github.com/krb5/krb5/commit/d22ed9b9216b35b0bd7cc0bfc9fea37060c273ec
commit d22ed9b9216b35b0bd7cc0bfc9fea37060c273ec
Author: Ken Hornstein <kenh at cmf.nrl.navy.mil>
Date: Thu Sep 30 17:10:06 2021 -0400
Support KRB5_CERTAUTH_HWAUTH_PASS in certauth
If a certauth module returns KRB5_CERTAUTH_HWAUTH_PASS, the certauth
accumulator sets the hw-authent flag in the ticket (like it would for
KRB5_CERTAUTH_HWAUTH), but defers authorization to other modules (like
it would for KRB5_PLUGIN_NO_HANDLE).
[ghudson at mit.edu: simplify tests by removing the HWAUTH returns from
the test2 module and allowing it to pass by authenticating as nocert]
doc/plugindev/certauth.rst | 5 +-
src/lib/krb5/error_tables/k5e1_err.et | 1 +
src/plugins/certauth/test/certauth_test.exports | 1 +
src/plugins/certauth/test/main.c | 77 ++++++++++++++++++++---
src/plugins/preauth/pkinit/pkinit_srv.c | 6 +-
src/tests/t_certauth.py | 65 +++++++++++++------
6 files changed, 121 insertions(+), 34 deletions(-)
diff --git a/doc/plugindev/certauth.rst b/doc/plugindev/certauth.rst
index 3b715f7..7a7a077 100644
--- a/doc/plugindev/certauth.rst
+++ b/doc/plugindev/certauth.rst
@@ -18,7 +18,10 @@ authorization status and optionally outputs a list of authentication
indicator strings to be added to the ticket. Beginning in release
1.19, the authorize method can request that the hardware
authentication bit be set in the ticket by returning
-**KRB5_CERTAUTH_HWAUTH**. A module must use its own internal or
+**KRB5_CERTAUTH_HWAUTH**. Beginning in release 1.20, the authorize method
+can return **KRB5_CERTAUTH_HWAUTH_PASS** to request that the hardware
+authentication bit be set in the ticket but otherwise defer authorization
+to another certauth module. A module must use its own internal or
library-provided ASN.1 certificate decoder.
A module can optionally create and destroy module data with the
diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et
index abd9f3b..593d63e 100644
--- a/src/lib/krb5/error_tables/k5e1_err.et
+++ b/src/lib/krb5/error_tables/k5e1_err.et
@@ -43,4 +43,5 @@ error_code KRB5_KCM_RPC_ERROR, "Mach RPC error communicating with KCM daemon"
error_code KRB5_KCM_REPLY_TOO_BIG, "KCM daemon reply too big"
error_code KRB5_KCM_NO_SERVER, "No KCM server found"
error_code KRB5_CERTAUTH_HWAUTH, "Authorize and set hw-authent ticket flag"
+error_code KRB5_CERTAUTH_HWAUTH_PASS, "Set hw-authent ticket flag but do not authorize"
end
diff --git a/src/plugins/certauth/test/certauth_test.exports b/src/plugins/certauth/test/certauth_test.exports
index 1c8cd24..ecb9aa2 100644
--- a/src/plugins/certauth/test/certauth_test.exports
+++ b/src/plugins/certauth/test/certauth_test.exports
@@ -1,2 +1,3 @@
certauth_test1_initvt
certauth_test2_initvt
+certauth_test3_initvt
diff --git a/src/plugins/certauth/test/main.c b/src/plugins/certauth/test/main.c
index 7e7a3ef..eea5cc6 100644
--- a/src/plugins/certauth/test/main.c
+++ b/src/plugins/certauth/test/main.c
@@ -38,7 +38,7 @@ struct krb5_certauth_moddata_st {
int initialized;
};
-/* Test module 1 returns OK with an indicator. */
+/* Test module 1 passes with an indicator. */
static krb5_error_code
test1_authorize(krb5_context context, krb5_certauth_moddata moddata,
const uint8_t *cert, size_t cert_len,
@@ -131,9 +131,9 @@ has_cn(krb5_context context, const uint8_t *cert, size_t cert_len,
}
/*
- * Test module 2 returns OK if princ matches the CN part of the subject name,
- * and returns indicators of the module name and princ. If the "hwauth" string
- * attribute is set on db_entry, it returns KRB5_CERTAUTH_HWAUTH.
+ * Test module 2 passes if the principal name is "nocert". Otherwise it
+ * returns OK if the CN of the cert matches the principal name, with indicators
+ * of the module name and princ, and errors if the certificate does not match.
*/
static krb5_error_code
test2_authorize(krb5_context context, krb5_certauth_moddata moddata,
@@ -143,7 +143,7 @@ test2_authorize(krb5_context context, krb5_certauth_moddata moddata,
char ***authinds_out)
{
krb5_error_code ret;
- char *name = NULL, *strval = NULL, **ais = NULL;
+ char *name = NULL, **ais = NULL;
*authinds_out = NULL;
@@ -153,6 +153,10 @@ test2_authorize(krb5_context context, krb5_certauth_moddata moddata,
KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name);
if (ret)
goto cleanup;
+ if (strcmp(name, "nocert") == 0) {
+ ret = KRB5_PLUGIN_NO_HANDLE;
+ goto cleanup;
+ }
if (!has_cn(context, cert, cert_len, name)) {
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
@@ -169,16 +173,50 @@ test2_authorize(krb5_context context, krb5_certauth_moddata moddata,
ais = NULL;
+cleanup:
+ krb5_free_unparsed_name(context, name);
+ return ret;
+}
+
+/*
+ * Test module 3 reads the "hwauth" string attribute on db_entry, and adds an
+ * authentication indicator of "hwauth:<value>" if the attribute is set. It
+ * returns KRB5_CERTAUTH_HWAUTH if the value is "ok", and
+ * KRB5_CERTAUTH_HWAUTH_PASS if the value is "pass". Otherwise it passes.
+ */
+static krb5_error_code
+test3_authorize(krb5_context context, krb5_certauth_moddata moddata,
+ const uint8_t *cert, size_t cert_len,
+ krb5_const_principal princ, const void *opts,
+ const struct _krb5_db_entry_new *db_entry,
+ char ***authinds_out)
+{
+ krb5_error_code ret;
+ char *strval = NULL, **ais = NULL;
+
ret = krb5_dbe_get_string(context, (krb5_db_entry *)db_entry, "hwauth",
&strval);
if (ret)
- goto cleanup;
+ return ret;
+ if (strval != NULL && strcmp(strval, "ok") == 0)
+ ret = KRB5_CERTAUTH_HWAUTH;
+ else if (strval != NULL && strcmp(strval, "pass") == 0)
+ ret = KRB5_CERTAUTH_HWAUTH_PASS;
+ else
+ ret = KRB5_PLUGIN_NO_HANDLE;
- ret = (strval != NULL) ? KRB5_CERTAUTH_HWAUTH : 0;
- krb5_dbe_free_string(context, strval);
+ if (strval != NULL) {
+ ais = calloc(2, sizeof(*ais));
+ assert(ais != NULL);
+ if (asprintf(&ais[0], "hwauth:%s", strval) < 0)
+ abort();
+ assert(ais[0] != NULL);
+ }
-cleanup:
- krb5_free_unparsed_name(context, name);
+ *authinds_out = ais;
+ ais = NULL;
+
+ krb5_dbe_free_string(context, strval);
return ret;
}
@@ -219,3 +257,22 @@ certauth_test2_initvt(krb5_context context, int maj_ver, int min_ver,
vt->free_ind = test_free_ind;
return 0;
}
+
+krb5_error_code
+certauth_test3_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+certauth_test3_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 = "test3";
+ vt->authorize = test3_authorize;
+ vt->free_ind = test_free_ind;
+ return 0;
+}
diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c
index 3ae56c0..df0983e 100644
--- a/src/plugins/preauth/pkinit/pkinit_srv.c
+++ b/src/plugins/preauth/pkinit/pkinit_srv.c
@@ -349,8 +349,8 @@ authorize_cert(krb5_context context, certauth_handle *certauth_modules,
* Check the certificate against each certauth module. For the certificate
* to be authorized at least one module must return 0 or
* KRB5_CERTAUTH_HWAUTH, and no module can return an error code other than
- * KRB5_PLUGIN_NO_HANDLE (pass). Add indicators from modules that return 0
- * or pass.
+ * KRB5_PLUGIN_NO_HANDLE (pass) or KRB5_CERTAUTH_HWAUTH_PASS (pass but
+ * set hw-authent). Add indicators from all modules.
*/
ret = KRB5_PLUGIN_NO_HANDLE;
for (i = 0; certauth_modules != NULL && certauth_modules[i] != NULL; i++) {
@@ -362,6 +362,8 @@ authorize_cert(krb5_context context, certauth_handle *certauth_modules,
accepted = TRUE;
else if (ret == KRB5_CERTAUTH_HWAUTH)
accepted = hwauth = TRUE;
+ else if (ret == KRB5_CERTAUTH_HWAUTH_PASS)
+ hwauth = TRUE;
else if (ret != KRB5_PLUGIN_NO_HANDLE)
goto cleanup;
diff --git a/src/tests/t_certauth.py b/src/tests/t_certauth.py
index c235e99..06a5399 100644
--- a/src/tests/t_certauth.py
+++ b/src/tests/t_certauth.py
@@ -15,8 +15,10 @@ modpath = os.path.join(buildtop, 'plugins', 'certauth', 'test',
pkinit_krb5_conf = {'realms': {'$realm': {
'pkinit_anchors': 'FILE:%s' % ca_pem}},
'plugins': {'certauth': {'module': ['test1:' + modpath,
- 'test2:' + modpath],
- 'enable_only': ['test1', 'test2']}}}
+ 'test2:' + modpath,
+ 'test3:' + modpath],
+ 'enable_only': ['test1', 'test2',
+ 'test3']}}}
pkinit_kdc_conf = {'realms': {'$realm': {
'default_principal_flags': '+preauth',
'pkinit_eku_checking': 'none',
@@ -27,33 +29,54 @@ file_identity = 'FILE:%s,%s' % (user_pem, privkey_pem)
realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=pkinit_kdc_conf,
get_creds=False)
+realm.addprinc('nocert')
-# Let the test module match user to CN=user, with indicators.
-realm.kinit(realm.user_princ,
- flags=['-X', 'X509_user_identity=%s' % file_identity])
+def pkinit(princ, **kw):
+ realm.kinit(princ, flags=['-X', 'X509_user_identity=%s' % file_identity],
+ **kw)
+
+def check_indicators(inds):
+ msg = '+97: [%s]' % inds
+ realm.run(['./adata', realm.host_princ], expected_msg=msg)
+
+# Test that authentication fails if no module accepts.
+pkinit('nocert', expected_code=1, expected_msg='Client name mismatch')
+
+# Let the test2 module match user to CN=user, with indicators.
+pkinit(realm.user_princ)
realm.klist(realm.user_princ)
-realm.run([kvno, realm.host_princ])
-realm.run(['./adata', realm.host_princ],
- expected_msg='+97: [test1, test2, user, indpkinit1, indpkinit2]')
+check_indicators('test1, test2, user, indpkinit1, indpkinit2')
-# Let the test module mismatch with user2 to CN=user.
-realm.addprinc("user2 at KRBTEST.COM")
-out = realm.kinit("user2 at KRBTEST.COM",
- flags=['-X', 'X509_user_identity=%s' % file_identity],
- expected_code=1,
- expected_msg='kinit: Certificate mismatch')
+# Let the test2 module mismatch with user2 to CN=user.
+realm.addprinc('user2 at KRBTEST.COM')
+pkinit('user2', expected_code=1, expected_msg='kinit: Certificate mismatch')
# Test the KRB5_CERTAUTH_HWAUTH return code.
mark('hw-authent flag tests')
# First test +requires_hwauth without causing the hw-authent ticket
# flag to be set. This currently results in a preauth loop.
realm.run([kadminl, 'modprinc', '+requires_hwauth', realm.user_princ])
-realm.kinit(realm.user_princ,
- flags=['-X', 'X509_user_identity=%s' % file_identity],
- expected_code=1, expected_msg='Looping detected')
-# Cause the test2 module to return KRB5_CERTAUTH_HWAUTH and try again.
-realm.run([kadminl, 'setstr', realm.user_princ, 'hwauth', 'x'])
-realm.kinit(realm.user_princ,
- flags=['-X', 'X509_user_identity=%s' % file_identity])
+pkinit(realm.user_princ, expected_code=1, expected_msg='Looping detected')
+# Cause the test3 module to return KRB5_CERTAUTH_HWAUTH and try again.
+# Authentication should succeed whether or not another module accepts,
+# but not if another module rejects.
+realm.run([kadminl, 'setstr', realm.user_princ, 'hwauth', 'ok'])
+realm.run([kadminl, 'setstr', 'user2', 'hwauth', 'ok'])
+realm.run([kadminl, 'setstr', 'nocert', 'hwauth', 'ok'])
+pkinit(realm.user_princ)
+check_indicators('test1, test2, user, hwauth:ok, indpkinit1, indpkinit2')
+pkinit('user2', expected_code=1, expected_msg='kinit: Certificate mismatch')
+pkinit('nocert')
+check_indicators('test1, hwauth:ok, indpkinit1, indpkinit2')
+
+# Cause the test3 module to return KRB5_CERTAUTH_HWAUTH_PASS and try
+# again. Authentication should succeed only if another module accepts.
+realm.run([kadminl, 'setstr', realm.user_princ, 'hwauth', 'pass'])
+realm.run([kadminl, 'setstr', 'user2', 'hwauth', 'pass'])
+realm.run([kadminl, 'setstr', 'nocert', 'hwauth', 'pass'])
+pkinit(realm.user_princ)
+check_indicators('test1, test2, user, hwauth:pass, indpkinit1, indpkinit2')
+pkinit('user2', expected_code=1, expected_msg='kinit: Certificate mismatch')
+pkinit('nocert', expected_code=1, expected_msg='kinit: Client name mismatch')
success("certauth tests")
More information about the cvs-krb5
mailing list