github.com/jcmturner/gokrb5/v8@v8.4.4/spnego/spnego.go (about)

     1  // Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication.
     2  package spnego
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/jcmturner/gofork/encoding/asn1"
    10  	"github.com/jcmturner/gokrb5/v8/asn1tools"
    11  	"github.com/jcmturner/gokrb5/v8/client"
    12  	"github.com/jcmturner/gokrb5/v8/gssapi"
    13  	"github.com/jcmturner/gokrb5/v8/keytab"
    14  	"github.com/jcmturner/gokrb5/v8/service"
    15  )
    16  
    17  // SPNEGO implements the GSS-API mechanism for RFC 4178
    18  type SPNEGO struct {
    19  	serviceSettings *service.Settings
    20  	client          *client.Client
    21  	spn             string
    22  }
    23  
    24  // SPNEGOClient configures the SPNEGO mechanism suitable for client side use.
    25  func SPNEGOClient(cl *client.Client, spn string) *SPNEGO {
    26  	s := new(SPNEGO)
    27  	s.client = cl
    28  	s.spn = spn
    29  	s.serviceSettings = service.NewSettings(nil, service.SName(spn))
    30  	return s
    31  }
    32  
    33  // SPNEGOService configures the SPNEGO mechanism suitable for service side use.
    34  func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO {
    35  	s := new(SPNEGO)
    36  	s.serviceSettings = service.NewSettings(kt, options...)
    37  	return s
    38  }
    39  
    40  // OID returns the GSS-API assigned OID for SPNEGO.
    41  func (s *SPNEGO) OID() asn1.ObjectIdentifier {
    42  	return gssapi.OIDSPNEGO.OID()
    43  }
    44  
    45  // AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO.
    46  func (s *SPNEGO) AcquireCred() error {
    47  	return s.client.AffirmLogin()
    48  }
    49  
    50  // InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos.
    51  func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) {
    52  	tkt, key, err := s.client.GetServiceTicket(s.spn)
    53  	if err != nil {
    54  		return &SPNEGOToken{}, err
    55  	}
    56  	negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key)
    57  	if err != nil {
    58  		return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err)
    59  	}
    60  	return &SPNEGOToken{
    61  		Init:         true,
    62  		NegTokenInit: negTokenInit,
    63  		settings:     s.serviceSettings,
    64  	}, nil
    65  }
    66  
    67  // AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and
    68  // establish a context.
    69  func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) {
    70  	var ctx context.Context
    71  	t, ok := ct.(*SPNEGOToken)
    72  	if !ok {
    73  		return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"}
    74  	}
    75  	t.settings = s.serviceSettings
    76  	var oid asn1.ObjectIdentifier
    77  	if t.Init {
    78  		oid = t.NegTokenInit.MechTypes[0]
    79  	}
    80  	if t.Resp {
    81  		oid = t.NegTokenResp.SupportedMech
    82  	}
    83  	if !(oid.Equal(gssapi.OIDKRB5.OID()) || oid.Equal(gssapi.OIDMSLegacyKRB5.OID())) {
    84  		return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"}
    85  	}
    86  	// Flags in the NegInit must be used 	t.NegTokenInit.ReqFlags
    87  	ok, status := t.Verify()
    88  	ctx = t.Context()
    89  	return ok, ctx, status
    90  }
    91  
    92  // Log will write to the service's logger if it is configured.
    93  func (s *SPNEGO) Log(format string, v ...interface{}) {
    94  	if s.serviceSettings.Logger() != nil {
    95  		s.serviceSettings.Logger().Output(2, fmt.Sprintf(format, v...))
    96  	}
    97  }
    98  
    99  // SPNEGOToken is a GSS-API context token
   100  type SPNEGOToken struct {
   101  	Init         bool
   102  	Resp         bool
   103  	NegTokenInit NegTokenInit
   104  	NegTokenResp NegTokenResp
   105  	settings     *service.Settings
   106  	context      context.Context
   107  }
   108  
   109  // Marshal SPNEGO context token
   110  func (s *SPNEGOToken) Marshal() ([]byte, error) {
   111  	var b []byte
   112  	if s.Init {
   113  		hb, _ := asn1.Marshal(gssapi.OIDSPNEGO.OID())
   114  		tb, err := s.NegTokenInit.Marshal()
   115  		if err != nil {
   116  			return b, fmt.Errorf("could not marshal NegTokenInit: %v", err)
   117  		}
   118  		b = append(hb, tb...)
   119  		return asn1tools.AddASNAppTag(b, 0), nil
   120  	}
   121  	if s.Resp {
   122  		b, err := s.NegTokenResp.Marshal()
   123  		if err != nil {
   124  			return b, fmt.Errorf("could not marshal NegTokenResp: %v", err)
   125  		}
   126  		return b, nil
   127  	}
   128  	return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp")
   129  }
   130  
   131  // Unmarshal SPNEGO context token
   132  func (s *SPNEGOToken) Unmarshal(b []byte) error {
   133  	var r []byte
   134  	var err error
   135  	// We need some data in the array
   136  	if len(b) < 1 {
   137  		return fmt.Errorf("provided byte array is empty")
   138  	}
   139  	if b[0] != byte(161) {
   140  		// Not a NegTokenResp/Targ could be a NegTokenInit
   141  		var oid asn1.ObjectIdentifier
   142  		r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
   143  		if err != nil {
   144  			return fmt.Errorf("not a valid SPNEGO token: %v", err)
   145  		}
   146  		// Check the OID is the SPNEGO OID value
   147  		SPNEGOOID := gssapi.OIDSPNEGO.OID()
   148  		if !oid.Equal(SPNEGOOID) {
   149  			return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String())
   150  		}
   151  	} else {
   152  		// Could be a NegTokenResp/Targ
   153  		r = b
   154  	}
   155  
   156  	_, nt, err := UnmarshalNegToken(r)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	switch v := nt.(type) {
   161  	case NegTokenInit:
   162  		s.Init = true
   163  		s.NegTokenInit = v
   164  		s.NegTokenInit.settings = s.settings
   165  	case NegTokenResp:
   166  		s.Resp = true
   167  		s.NegTokenResp = v
   168  		s.NegTokenResp.settings = s.settings
   169  	default:
   170  		return errors.New("unknown choice type for NegotiationToken")
   171  	}
   172  	return nil
   173  }
   174  
   175  // Verify the SPNEGOToken
   176  func (s *SPNEGOToken) Verify() (bool, gssapi.Status) {
   177  	if (!s.Init && !s.Resp) || (s.Init && s.Resp) {
   178  		return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"}
   179  	}
   180  	if s.Init {
   181  		s.NegTokenInit.settings = s.settings
   182  		ok, status := s.NegTokenInit.Verify()
   183  		if ok {
   184  			s.context = s.NegTokenInit.Context()
   185  		}
   186  		return ok, status
   187  	}
   188  	if s.Resp {
   189  		s.NegTokenResp.settings = s.settings
   190  		ok, status := s.NegTokenResp.Verify()
   191  		if ok {
   192  			s.context = s.NegTokenResp.Context()
   193  		}
   194  		return ok, status
   195  	}
   196  	// should not be possible to get here
   197  	return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"}
   198  }
   199  
   200  // Context returns the SPNEGO context which will contain any verify user identity information.
   201  func (s *SPNEGOToken) Context() context.Context {
   202  	return s.context
   203  }