krb5 commit: Add k5test.py facilities for PKINIT

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/727627036ccba5f1c4c2b9ce6949fdc3172fc684
commit 727627036ccba5f1c4c2b9ce6949fdc3172fc684
Author: Greg Hudson <[email protected]>
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('[email protected]')
-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 [email protected]).
+
 * 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')
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.