krb5 commit: Add k5test.py facilities for PKINIT

Greg Hudson ghudson at mit.edu
Thu Jan 27 16:57:30 EST 2022


https://github.com/krb5/krb5/commit/727627036ccba5f1c4c2b9ce6949fdc3172fc684
commit 727627036ccba5f1c4c2b9ce6949fdc3172fc684
Author: Greg Hudson <ghudson at mit.edu>
Date:   Mon Jan 24 11:20:12 2022 -0500

    Add k5test.py facilities for PKINIT
    
    Add the global variables pkinit_enabled and pkinit_certs.  Add the
    realm flag pkinit=True.  Add the realm method pkinit().  Use these
    facilities in t_pkinit.py, t_certauth.py, and t_authdata.py.

 src/tests/t_authdata.py |   12 +-------
 src/tests/t_certauth.py |   57 +++++++++++++++----------------------------
 src/tests/t_pkinit.py   |   62 ++++++++++++++++-------------------------------
 src/util/k5test.py      |   35 +++++++++++++++++++++++++-
 4 files changed, 76 insertions(+), 90 deletions(-)

diff --git a/src/tests/t_authdata.py b/src/tests/t_authdata.py
index fef8a79..cea5007 100644
--- a/src/tests/t_authdata.py
+++ b/src/tests/t_authdata.py
@@ -51,19 +51,11 @@ if '128:' not in out or  '^-42: Hello' not in out or ' -3: test' not in out:
 
 realm.stop()
 
-if not os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so')):
+if not pkinit_enabled:
     skipped('anonymous ticket authdata tests', 'PKINIT not built')
 else:
     # Set up a realm with PKINIT support and get anonymous tickets.
-    certs = os.path.join(srctop, 'tests', 'pkinit-certs')
-    ca_pem = os.path.join(certs, 'ca.pem')
-    kdc_pem = os.path.join(certs, 'kdc.pem')
-    privkey_pem = os.path.join(certs, 'privkey.pem')
-    pkinit_conf = {'realms': {'$realm': {
-                'pkinit_anchors': 'FILE:%s' % ca_pem,
-                'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem)}}}
-    conf.update(pkinit_conf)
-    realm = K5Realm(krb5_conf=conf, get_creds=False)
+    realm = K5Realm(krb5_conf=conf, get_creds=False, pkinit=True)
     realm.addprinc('WELLKNOWN/ANONYMOUS')
     realm.kinit('@%s' % realm.realm, flags=['-n'])
 
diff --git a/src/tests/t_certauth.py b/src/tests/t_certauth.py
index 06a5399..82a98a8 100644
--- a/src/tests/t_certauth.py
+++ b/src/tests/t_certauth.py
@@ -1,72 +1,55 @@
 from k5test import *
 
 # Skip this test if pkinit wasn't built.
-if not os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so')):
+if not pkinit_enabled:
     skip_rest('certauth tests', 'PKINIT module not built')
 
-certs = os.path.join(srctop, 'tests', 'pkinit-certs')
-ca_pem = os.path.join(certs, 'ca.pem')
-kdc_pem = os.path.join(certs, 'kdc.pem')
-privkey_pem = os.path.join(certs, 'privkey.pem')
-user_pem = os.path.join(certs, 'user.pem')
-
 modpath = os.path.join(buildtop, 'plugins', 'certauth', 'test',
                        'certauth_test.so')
-pkinit_krb5_conf = {'realms': {'$realm': {
-            'pkinit_anchors': 'FILE:%s' % ca_pem}},
-            'plugins': {'certauth': {'module': ['test1:' + modpath,
-                                                'test2:' + modpath,
-                                                'test3:' + modpath],
-                                     'enable_only': ['test1', 'test2',
-                                                     'test3']}}}
-pkinit_kdc_conf = {'realms': {'$realm': {
-            'default_principal_flags': '+preauth',
-            'pkinit_eku_checking': 'none',
-            'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem),
-            'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}}
-
-file_identity = 'FILE:%s,%s' % (user_pem, privkey_pem)
+krb5_conf = {'plugins': {'certauth': {
+    'module': ['test1:' + modpath, 'test2:' + modpath, 'test3:' + modpath],
+    'enable_only': ['test1', 'test2', 'test3']}}}
+kdc_conf = {'realms': {'$realm': {
+    'default_principal_flags': '+preauth',
+    'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}}
 
-realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=pkinit_kdc_conf,
-                get_creds=False)
+realm = K5Realm(krb5_conf=krb5_conf, kdc_conf=kdc_conf, get_creds=False,
+                pkinit=True)
 realm.addprinc('nocert')
 
-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')
+realm.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.pkinit(realm.user_princ)
 realm.klist(realm.user_princ)
 check_indicators('test1, test2, user, indpkinit1, indpkinit2')
 
 # 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')
+realm.pkinit('user2', expected_code=1, expected_msg='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])
-pkinit(realm.user_princ, expected_code=1, expected_msg='Looping detected')
+realm.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)
+realm.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')
+realm.pkinit('user2', expected_code=1, expected_msg='Certificate mismatch')
+realm.pkinit('nocert')
 check_indicators('test1, hwauth:ok, indpkinit1, indpkinit2')
 
 # Cause the test3 module to return KRB5_CERTAUTH_HWAUTH_PASS and try
@@ -74,9 +57,9 @@ check_indicators('test1, hwauth:ok, indpkinit1, indpkinit2')
 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)
+realm.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')
+realm.pkinit('user2', expected_code=1, expected_msg='Certificate mismatch')
+realm.pkinit('nocert', expected_code=1, expected_msg='Client name mismatch')
 
 success("certauth tests")
diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py
index 3d90e76..ec2356e 100755
--- a/src/tests/t_pkinit.py
+++ b/src/tests/t_pkinit.py
@@ -1,33 +1,27 @@
 from k5test import *
 
 # Skip this test if pkinit wasn't built.
-if not os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so')):
+if not pkinit_enabled:
     skip_rest('PKINIT tests', 'PKINIT module not built')
 
 soft_pkcs11 = os.path.join(buildtop, 'tests', 'softpkcs11', 'softpkcs11.so')
 
 # Construct a krb5.conf fragment configuring pkinit.
-certs = os.path.join(srctop, 'tests', 'pkinit-certs')
-ca_pem = os.path.join(certs, 'ca.pem')
-kdc_pem = os.path.join(certs, 'kdc.pem')
-user_pem = os.path.join(certs, 'user.pem')
-privkey_pem = os.path.join(certs, 'privkey.pem')
-privkey_enc_pem = os.path.join(certs, 'privkey-enc.pem')
-user_p12 = os.path.join(certs, 'user.p12')
-user_enc_p12 = os.path.join(certs, 'user-enc.p12')
-user_upn_p12 = os.path.join(certs, 'user-upn.p12')
-user_upn2_p12 = os.path.join(certs, 'user-upn2.p12')
-user_upn3_p12 = os.path.join(certs, 'user-upn3.p12')
-generic_p12 = os.path.join(certs, 'generic.p12')
+user_pem = os.path.join(pkinit_certs, 'user.pem')
+privkey_pem = os.path.join(pkinit_certs, 'privkey.pem')
+privkey_enc_pem = os.path.join(pkinit_certs, 'privkey-enc.pem')
+user_p12 = os.path.join(pkinit_certs, 'user.p12')
+user_enc_p12 = os.path.join(pkinit_certs, 'user-enc.p12')
+user_upn_p12 = os.path.join(pkinit_certs, 'user-upn.p12')
+user_upn2_p12 = os.path.join(pkinit_certs, 'user-upn2.p12')
+user_upn3_p12 = os.path.join(pkinit_certs, 'user-upn3.p12')
+generic_p12 = os.path.join(pkinit_certs, 'generic.p12')
 path = os.path.join(os.getcwd(), 'testdir', 'tmp-pkinit-certs')
 path_enc = os.path.join(os.getcwd(), 'testdir', 'tmp-pkinit-certs-enc')
 
-pkinit_krb5_conf = {'realms': {'$realm': {
-            'pkinit_anchors': 'FILE:%s' % ca_pem}}}
 pkinit_kdc_conf = {'realms': {'$realm': {
             'default_principal_flags': '+preauth',
             'pkinit_eku_checking': 'none',
-            'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem),
             'pkinit_indicator': ['indpkinit1', 'indpkinit2']}}}
 restrictive_kdc_conf = {'realms': {'$realm': {
             'restrict_anonymous_to_tgt': 'true' }}}
@@ -41,7 +35,6 @@ alias_kdc_conf = {'realms': {'$realm': {
             'default_principal_flags': '+preauth',
             'pkinit_eku_checking': 'none',
             'pkinit_allow_upn': 'true',
-            'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem),
             'database_module': 'test'}},
                   'dbmodules': {'test': {
                       'db_library': 'test',
@@ -67,8 +60,7 @@ p11_token_identity = ('PKCS11:module_name=' + soft_pkcs11 +
                       ':slotid=1:token=SoftToken (token)')
 
 # Start a realm with the test kdb module for the following UPN SAN tests.
-realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=alias_kdc_conf,
-                create_kdb=False)
+realm = K5Realm(kdc_conf=alias_kdc_conf, create_kdb=False, pkinit=True)
 realm.start_kdc()
 
 mark('UPN SANs')
@@ -104,8 +96,7 @@ realm.run([kinit, '-X', 'X509_user_identity=%s' % p12_upn2_identity, 'user2'],
           expected_code=1, expected_msg=msg)
 realm.stop()
 
-realm = K5Realm(krb5_conf=pkinit_krb5_conf, kdc_conf=pkinit_kdc_conf,
-                get_creds=False)
+realm = K5Realm(kdc_conf=pkinit_kdc_conf, get_creds=False, pkinit=True)
 
 # Sanity check - password-based preauth should still work.
 mark('password preauth sanity check')
@@ -177,9 +168,7 @@ msgs = ('Sending unauthenticated request',
         'PKINIT client verified DH reply',
         'PKINIT client found id-pkinit-san in KDC cert',
         'PKINIT client matched KDC principal krbtgt/')
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity],
-            expected_trace=msgs)
+realm.pkinit(realm.user_princ, expected_trace=msgs)
 realm.klist(realm.user_princ)
 realm.run([kvno, realm.host_princ])
 
@@ -192,11 +181,9 @@ realm.kinit(realm.user_princ, expected_trace=msgs, env=id_env)
 
 # Try again using RSA instead of DH.
 mark('FILE identity, no password, RSA')
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity,
-                   '-X', 'flag_RSA_PROTOCOL=yes'],
-            expected_trace=('PKINIT client making RSA request',
-                            'PKINIT client verified RSA reply'))
+realm.pkinit(realm.user_princ, flags=['-X', 'flag_RSA_PROTOCOL=yes'],
+             expected_trace=('PKINIT client making RSA request',
+                             'PKINIT client verified RSA reply'))
 realm.klist(realm.user_princ)
 
 # Test a DH parameter renegotiation by temporarily setting a 4096-bit
@@ -217,25 +204,18 @@ msgs = ('Sending unauthenticated request',
         'trying again with KDC-provided parameters',
         'Preauth module pkinit (16) tryagain returned: 0/Success',
         ' preauth for next request: PA-PK-AS-REQ (16), PA-FX-COOKIE (133)')
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity],
-            expected_trace=msgs)
+realm.pkinit(realm.user_princ, expected_trace=msgs)
 
 # Test enforcement of required freshness tokens.  (We can leave
 # freshness tokens required after this test.)
 mark('freshness token enforcement')
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity,
-                   '-X', 'disable_freshness=yes'])
+realm.pkinit(realm.user_princ, flags=['-X', 'disable_freshness=yes'])
 f_env = realm.special_env('freshness', True, kdc_conf=freshness_kdc_conf)
 realm.stop_kdc()
 realm.start_kdc(env=f_env)
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity])
-realm.kinit(realm.user_princ,
-            flags=['-X', 'X509_user_identity=%s' % file_identity,
-                   '-X', 'disable_freshness=yes'],
-            expected_code=1, expected_msg='Preauthentication failed')
+realm.pkinit(realm.user_princ)
+realm.pkinit(realm.user_princ, flags=['-X', 'disable_freshness=yes'],
+             expected_code=1, expected_msg='Preauthentication failed')
 # Anonymous should never require a freshness token.
 realm.kinit('@%s' % realm.realm, flags=['-n', '-X', 'disable_freshness=yes'])
 
diff --git a/src/util/k5test.py b/src/util/k5test.py
index 1917bbc..bd71a45 100644
--- a/src/util/k5test.py
+++ b/src/util/k5test.py
@@ -81,6 +81,7 @@ keyword arguments:
     - $buildtop: The root of the build directory
     - $srctop:   The root of the source directory
     - $plugins:  The plugin directory in the build tree
+    - $certs:    The PKINIT certificate directory in the source tree
     - $hostname: The FQDN of the host
     - $port0:    The first listener port (portbase)
     - ...
@@ -121,6 +122,8 @@ keyword arguments:
 * bdb_only=True: Use the DB2 KDB module even if K5TEST_LMDB is set in
   the environment.
 
+* pkinit=True: Configure a PKINIT anchor and KDC certificate.
+
 Scripts may use the following functions and variables:
 
 * fail(message): Display message (plus leading marker and trailing
@@ -197,6 +200,11 @@ Scripts may use the following functions and variables:
 
 * plugins: The plugin directory in the build tree (absolute path).
 
+* pkinit_enabled: True if the PKINIT plugin module is present in the
+  build directory.
+
+* pkinit_certs: The directory containing test PKINIT certificates.
+
 * hostname: The local hostname as it will initially appear in
   krb5_sname_to_principal() results.  (Shortname qualification is
   turned off in the test environment to make this value easy to
@@ -302,6 +310,10 @@ Scripts may use the following realm methods and attributes:
   flags must cause kinit not to need a password (e.g. by specifying a
   keytab).
 
+* realm.pkinit(princ, **keywords): Acquire credentials for princ,
+  supplying a PKINIT identity of the basic user test certificate
+  (matching user at KRBTEST.COM).
+
 * realm.klist(client_princ, service_princ=None, ccache=None): Using
   klist, list the credentials cache ccache (must be a filename;
   self.ccache if not specified) and verify that the output shows
@@ -881,7 +893,7 @@ class K5Realm(object):
                  krb5_conf=None, kdc_conf=None, create_kdb=True,
                  krbtgt_keysalt=None, create_user=True, get_creds=True,
                  create_host=True, start_kdc=True, start_kadmind=False,
-                 start_kpropd=False, bdb_only=False):
+                 start_kpropd=False, bdb_only=False, pkinit=False):
         global hostname, _default_krb5_conf, _default_kdc_conf
         global _lmdb_kdc_conf, _current_db
 
@@ -898,11 +910,15 @@ class K5Realm(object):
         self.ccache = os.path.join(self.testdir, 'ccache')
         self.gss_mech_config = os.path.join(self.testdir, 'mech.conf')
         self.kadmin_ccache = os.path.join(self.testdir, 'kadmin_ccache')
-        self._krb5_conf = _cfg_merge(_default_krb5_conf, krb5_conf)
+        base_krb5_conf = _default_krb5_conf
         base_kdc_conf = _default_kdc_conf
         if (os.getenv('K5TEST_LMDB') is not None and
             not bdb_only and not _current_db):
             base_kdc_conf = _cfg_merge(base_kdc_conf, _lmdb_kdc_conf)
+        if pkinit:
+            base_krb5_conf = _cfg_merge(base_krb5_conf, _pkinit_krb5_conf)
+            base_kdc_conf = _cfg_merge(base_kdc_conf, _pkinit_kdc_conf)
+        self._krb5_conf = _cfg_merge(base_krb5_conf, krb5_conf)
         self._kdc_conf = _cfg_merge(base_kdc_conf, kdc_conf)
         self._kdc_proc = None
         self._kadmind_proc = None
@@ -979,6 +995,7 @@ class K5Realm(object):
                                     buildtop=buildtop,
                                     srctop=srctop,
                                     plugins=plugins,
+                                    certs=pkinit_certs,
                                     hostname=hostname,
                                     port0=self.portbase,
                                     port1=self.portbase + 1,
@@ -1120,6 +1137,12 @@ class K5Realm(object):
             input = None
         return self.run([kinit] + flags + [princname], input=input, **keywords)
 
+    def pkinit(self, princ, flags=[], **kw):
+        id = 'FILE:%s,%s' % (os.path.join(pkinit_certs, 'user.pem'),
+                             os.path.join(pkinit_certs, 'privkey.pem'))
+        flags = flags + ['-X', 'X509_user_identity=%s' % id]
+        self.kinit(princ, flags=flags, **kw)
+
     def klist(self, client_princ, service_princ=None, ccache=None, **keywords):
         if service_princ is None:
             service_princ = self.krbtgt_princ
@@ -1302,6 +1325,12 @@ _lmdb_kdc_conf = {'dbmodules': {'db': {'db_library': 'klmdb',
                                        'nosync': 'true'}}}
 
 
+_pkinit_krb5_conf = {'realms': {'$realm': {
+    'pkinit_anchors': 'FILE:$certs/ca.pem'}}}
+_pkinit_kdc_conf = {'realms': {'$realm': {
+    'pkinit_identity': 'FILE:$certs/kdc.pem,$certs/privkey.pem'}}}
+
+
 # A pass is a tuple of: name, krbtgt_keysalt, krb5_conf, kdc_conf.
 _passes = [
     # No special settings; exercises AES256.
@@ -1370,6 +1399,8 @@ _last_cmd_output = None
 buildtop = _find_buildtop()
 srctop = _find_srctop()
 plugins = os.path.join(buildtop, 'plugins')
+pkinit_enabled = os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so'))
+pkinit_certs = os.path.join(srctop, 'tests', 'pkinit-certs')
 hostname = socket.gethostname().lower()
 null_input = open(os.devnull, 'r')
 


More information about the cvs-krb5 mailing list