From 3f94e9aa811cfff033a8d5438b7679ce0fa55b73 Mon Sep 17 00:00:00 2001
From: Volodymyr Khomenko <volodymyr@vastdata.com>
Date: Wed, 17 Nov 2021 23:58:20 +0200
Subject: [PATCH 1/2] Fixed gssapi usage (RPCGSS) for nfs4.0 server test

gssapi library used in the code has been changed and current code is not
compatible with API of new library version.  Fixed the code
to work with recent gssapi (tested with 1.6.2).
Tested with krb5, krb5i and krb5p security, like this:
nfs4.0/testserver.py server.fqdn:/export gss noGSS8 --security=krb5
Note - GSS8 is known to cause the problems for other tests that run after it,
however it's safe to run it alone.

Signed-off-by: Volodymyr Khomenko <volodymyr@vastdata.com>
---
 nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py | 116 ++++++++++----------------
 1 file changed, 45 insertions(+), 71 deletions(-)

diff --git a/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py b/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py
index 1b5eb93..6577fcf 100644
--- a/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py
+++ b/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py
@@ -1,8 +1,8 @@
 from .base import SecFlavor, SecError
 from rpc.rpc_const import RPCSEC_GSS
 from rpc.rpc_type import opaque_auth
-from gss_const import *
-import gss_pack
+from .gss_const import *
+from . import gss_pack
 import gss_type
 import gssapi
 import threading
@@ -125,50 +125,41 @@ class SecAuthGss(SecFlavor):
     def initialize(self, client): # Note this is not thread safe
         """Set seq_num, init, handle, and context"""
         self.gss_seq_num = 0
-        d = gssapi.importName("nfs@%s" % client.remotehost)
-        if d['major'] != gssapi.GSS_S_COMPLETE:
-            raise SecError("gssapi.importName returned: %s" % \
-                  show_major(d['major']))
-        name = d['name']
-        # We need to send NULLPROCs with token from initSecContext
-        good_major = [gssapi.GSS_S_COMPLETE, gssapi.GSS_S_CONTINUE_NEEDED]
+        name = gssapi.Name("nfs@%s" % client.remotehost, gssapi.NameType.hostbased_service)
+        # We need to send NULLPROCs with token from SecurityContext
+        good_major = [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]
         self.init = 1
-        reply_token = None
-        reply_major = ''
-        context = None
+        input_token = None
+
+        # RFC2203 5.2.2.  Context Creation Requests
+        # When GSS_Init_sec_context() is called, the parameters
+        # replay_det_req_flag and sequence_req_flag must be turned off.
+
+        # Note - by default, out_of_sequence_detection flag (sequence_req_flag) is used by gssapi.init_sec_context()
+        # and we have 'An expected per-message token was not received' error (GSS_S_GAP_TOKEN).
+        # To prevent this, we need to use default flags without out_of_sequence_detection bit.
+        flags = gssapi.IntEnumFlagSet(gssapi.RequirementFlag, [gssapi.RequirementFlag.mutual_authentication])
+        context = gssapi.SecurityContext(name=name, flags=flags)
         while True:
-            d = gssapi.initSecContext(name, context, reply_token)
-            major = d['major']
-            context = d['context']
-            if major not in good_major:
-                raise SecError("gssapi.initSecContext returned: %s" % \
-                      show_major(major))
-            if (major == gssapi.GSS_S_CONTINUE_NEEDED) and \
-                   (reply_major == gssapi.GSS_S_COMPLETE):
-                raise SecError("Unexpected GSS_S_COMPLETE from server")
-            token = d['token']
-            if reply_major != gssapi.GSS_S_COMPLETE:
-                # FRED - sec 5.2.2 of RFC 2203 mentions possibility that
-                # no token is returned.  But then how get handle?
-                p = self.getpacker()
-                p.reset()
-                p.pack_opaque(token)
-                data = p.get_buffer()
-                reply = client.call(0, data)
-                up = self.getunpacker()
-                up.reset(reply)
-                res = up.unpack_rpc_gss_init_res()
-                up.done()
-                reply_major = res.gss_major
-                if reply_major not in good_major:
-                    raise SecError("Server returned: %s" % \
-                          show_major(reply_major))
-                self.init = 2
-                reply_token = res.gss_token
-            if major == gssapi.GSS_S_COMPLETE:
-                if reply_major != gssapi.GSS_S_COMPLETE:
-                    raise SecError("Unexpected COMPLETE from client")
+            # note - gssapi will raise an exception here automatically in case of failure
+            output_token = context.step(input_token)
+            if context.complete:
                 break
+            p = self.getpacker()
+            p.reset()
+            p.pack_opaque(output_token)
+            data = p.get_buffer()
+            reply = client.call(0, data)
+            up = self.getunpacker()
+            up.reset(reply)
+            res = up.unpack_rpc_gss_init_res()
+            up.done()
+            reply_major = res.gss_major
+            if reply_major not in good_major:
+                raise SecError("Server returned: %s" % \
+                    show_major(reply_major))
+            self.init = 2
+            input_token = res.gss_token
         self.gss_context = context
         self.gss_handle = res.handle
         self.init = 0
@@ -176,7 +167,7 @@ class SecAuthGss(SecFlavor):
     def make_cred(self):
         """Credential sent with each RPC call"""
         if self.init == 1: # first call in context creation
-            cred = self._make_cred_gss('', rpc_gss_svc_none, RPCSEC_GSS_INIT)
+            cred = self._make_cred_gss(b'', rpc_gss_svc_none, RPCSEC_GSS_INIT)
         elif self.init > 1: # subsequent calls in context creation
             cred = self._make_cred_gss('', rpc_gss_svc_none,
                                   RPCSEC_GSS_CONTINUE_INIT)
@@ -237,12 +228,8 @@ class SecAuthGss(SecFlavor):
         if self.init:
             return self._none
         else:
-            d = gssapi.getMIC(self.gss_context, data)
-            major = d['major']
-            if major != gssapi.GSS_S_COMPLETE:
-                raise SecError("gssapi.getMIC returned: %s" % \
-                      show_major(major))
-            return opaque_auth(RPCSEC_GSS, d['token'])
+            token = self.gss_context.get_signature(data)
+            return opaque_auth(RPCSEC_GSS, token)
         
     def _make_cred_gss(self, handle, service, gss_proc=RPCSEC_GSS_DATA, seq=0):
         data = gss_type.rpc_gss_cred_vers_1_t(gss_proc, seq, service, handle)
@@ -264,13 +251,10 @@ class SecAuthGss(SecFlavor):
             p.reset()
             p.pack_uint(gss_cred.seq_num)
             data = p.get_buffer() + data
-            d = gssapi.getMIC(self.gss_context, data)
-            if d['major'] != gssapi.GSS_S_COMPLETE:
-                raise SecError("gssapi.getMIC returned: %s" % \
-                      show_major(d['major']))
+            token = self.gss_context.get_signature(data)
             p.reset()
             p.pack_opaque(data)
-            p.pack_opaque(d['token'])
+            p.pack_opaque(token)
             data = p.get_buffer()
         elif gss_cred.service == rpc_gss_svc_privacy:
             # data = opaque[wrap([gss_seq_num+data])]
@@ -278,12 +262,9 @@ class SecAuthGss(SecFlavor):
             p.reset()
             p.pack_uint(gss_cred.seq_num)
             data = p.get_buffer() + data
-            d = gssapi.wrap(self.gss_context, data)
-            if d['major'] != gssapi.GSS_S_COMPLETE:
-                raise SecError("gssapi.wrap returned: %s" % \
-                      show_major(d['major']))
+            wrap_data = self.gss_context.wrap(data, encrypt=True)
             p.reset()
-            p.pack_opaque(d['msg'])
+            p.pack_opaque(wrap_data.message)
             data = p.get_buffer()
         else:
             # Not really necessary, should have already raised XDRError
@@ -303,10 +284,7 @@ class SecAuthGss(SecFlavor):
             data = p.unpack_opaque()
             checksum = p.unpack_opaque()
             p.done()
-            d = gssapi.verifyMIC(self.gss_context, data, checksum)
-            if d['major'] != gssapi.GSS_S_COMPLETE:
-                raise SecError("gssapi.verifyMIC returned: %s" % \
-                      show_major(d['major']))
+            qop = self.gss_context.verify_signature(data, checksum)
             p.reset(data)
             seqnum = p.unpack_uint()
             if seqnum != gss_cred.seq_num:
@@ -320,11 +298,8 @@ class SecAuthGss(SecFlavor):
             p.reset(data)
             data = p.unpack_opaque()
             p.done()
-            d = gssapi.unwrap(self.gss_context, data)
-            if d['major'] != gssapi.GSS_S_COMPLETE:
-                raise SecError("gssapi.unwrap returned %s" % \
-                      show_major(d['major']))
-            p.reset(d['msg'])
+            data, encrypted, qop = self.gss_context.unwrap(data)
+            p.reset(data)
             seqnum = p.unpack_uint()
             if seqnum != gss_cred.seq_num:
                 raise SecError(\
@@ -373,8 +348,7 @@ class SecAuthGss(SecFlavor):
             p = self.getpacker()
             p.reset()
             p.pack_uint(cred.seq_num)
-            d = gssapi.verifyMIC(self.gss_context, p.get_buffer(), rverf.body)
-            #print("Verify(%i):"%cred.seq_num, show_major(d['major']), show_minor(d['minor']))
+            qop = self.gss_context.verify_signature(p.get_buffer(), rverf.body)
             
         else:
             pass
-- 
2.24.1

