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

     1  package spnego
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/jcmturner/gofork/encoding/asn1"
     9  	"github.com/jcmturner/gokrb5/v8/client"
    10  	"github.com/jcmturner/gokrb5/v8/gssapi"
    11  	"github.com/jcmturner/gokrb5/v8/messages"
    12  	"github.com/jcmturner/gokrb5/v8/service"
    13  	"github.com/jcmturner/gokrb5/v8/types"
    14  )
    15  
    16  // https://msdn.microsoft.com/en-us/library/ms995330.aspx
    17  
    18  // Negotiation state values.
    19  const (
    20  	NegStateAcceptCompleted  NegState = 0
    21  	NegStateAcceptIncomplete NegState = 1
    22  	NegStateReject           NegState = 2
    23  	NegStateRequestMIC       NegState = 3
    24  )
    25  
    26  // NegState is a type to indicate the SPNEGO negotiation state.
    27  type NegState int
    28  
    29  // NegTokenInit implements Negotiation Token of type Init.
    30  type NegTokenInit struct {
    31  	MechTypes      []asn1.ObjectIdentifier
    32  	ReqFlags       asn1.BitString
    33  	MechTokenBytes []byte
    34  	MechListMIC    []byte
    35  	mechToken      gssapi.ContextToken
    36  	settings       *service.Settings
    37  }
    38  
    39  type marshalNegTokenInit struct {
    40  	MechTypes      []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
    41  	ReqFlags       asn1.BitString          `asn1:"explicit,optional,tag:1"`
    42  	MechTokenBytes []byte                  `asn1:"explicit,optional,omitempty,tag:2"`
    43  	MechListMIC    []byte                  `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
    44  }
    45  
    46  // NegTokenResp implements Negotiation Token of type Resp/Targ
    47  type NegTokenResp struct {
    48  	NegState      asn1.Enumerated
    49  	SupportedMech asn1.ObjectIdentifier
    50  	ResponseToken []byte
    51  	MechListMIC   []byte
    52  	mechToken     gssapi.ContextToken
    53  	settings      *service.Settings
    54  }
    55  
    56  type marshalNegTokenResp struct {
    57  	NegState      asn1.Enumerated       `asn1:"explicit,tag:0"`
    58  	SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
    59  	ResponseToken []byte                `asn1:"explicit,optional,omitempty,tag:2"`
    60  	MechListMIC   []byte                `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
    61  }
    62  
    63  // NegTokenTarg implements Negotiation Token of type Resp/Targ
    64  type NegTokenTarg NegTokenResp
    65  
    66  // Marshal an Init negotiation token
    67  func (n *NegTokenInit) Marshal() ([]byte, error) {
    68  	m := marshalNegTokenInit{
    69  		MechTypes:      n.MechTypes,
    70  		ReqFlags:       n.ReqFlags,
    71  		MechTokenBytes: n.MechTokenBytes,
    72  		MechListMIC:    n.MechListMIC,
    73  	}
    74  	b, err := asn1.Marshal(m)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	nt := asn1.RawValue{
    79  		Tag:        0,
    80  		Class:      2,
    81  		IsCompound: true,
    82  		Bytes:      b,
    83  	}
    84  	nb, err := asn1.Marshal(nt)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return nb, nil
    89  }
    90  
    91  // Unmarshal an Init negotiation token
    92  func (n *NegTokenInit) Unmarshal(b []byte) error {
    93  	init, nt, err := UnmarshalNegToken(b)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if !init {
    98  		return errors.New("bytes were not that of a NegTokenInit")
    99  	}
   100  	nInit := nt.(NegTokenInit)
   101  	n.MechTokenBytes = nInit.MechTokenBytes
   102  	n.MechListMIC = nInit.MechListMIC
   103  	n.MechTypes = nInit.MechTypes
   104  	n.ReqFlags = nInit.ReqFlags
   105  	return nil
   106  }
   107  
   108  // Verify an Init negotiation token
   109  func (n *NegTokenInit) Verify() (bool, gssapi.Status) {
   110  	// Check if supported mechanisms are in the MechTypeList
   111  	var mtSupported bool
   112  	for _, m := range n.MechTypes {
   113  		if m.Equal(gssapi.OIDKRB5.OID()) || m.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
   114  			if n.mechToken == nil && n.MechTokenBytes == nil {
   115  				return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
   116  			}
   117  			mtSupported = true
   118  			break
   119  		}
   120  	}
   121  	if !mtSupported {
   122  		return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
   123  	}
   124  	// There should be some mechtoken bytes for a KRB5Token (other mech types are not supported)
   125  	mt := new(KRB5Token)
   126  	mt.settings = n.settings
   127  	if n.mechToken == nil {
   128  		err := mt.Unmarshal(n.MechTokenBytes)
   129  		if err != nil {
   130  			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
   131  		}
   132  		n.mechToken = mt
   133  	} else {
   134  		var ok bool
   135  		mt, ok = n.mechToken.(*KRB5Token)
   136  		if !ok {
   137  			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
   138  		}
   139  	}
   140  	// Verify the mechtoken
   141  	return n.mechToken.Verify()
   142  }
   143  
   144  // Context returns the SPNEGO context which will contain any verify user identity information.
   145  func (n *NegTokenInit) Context() context.Context {
   146  	if n.mechToken != nil {
   147  		mt, ok := n.mechToken.(*KRB5Token)
   148  		if !ok {
   149  			return nil
   150  		}
   151  		return mt.Context()
   152  	}
   153  	return nil
   154  }
   155  
   156  // Marshal a Resp/Targ negotiation token
   157  func (n *NegTokenResp) Marshal() ([]byte, error) {
   158  	m := marshalNegTokenResp{
   159  		NegState:      n.NegState,
   160  		SupportedMech: n.SupportedMech,
   161  		ResponseToken: n.ResponseToken,
   162  		MechListMIC:   n.MechListMIC,
   163  	}
   164  	b, err := asn1.Marshal(m)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	nt := asn1.RawValue{
   169  		Tag:        1,
   170  		Class:      2,
   171  		IsCompound: true,
   172  		Bytes:      b,
   173  	}
   174  	nb, err := asn1.Marshal(nt)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	return nb, nil
   179  }
   180  
   181  // Unmarshal a Resp/Targ negotiation token
   182  func (n *NegTokenResp) Unmarshal(b []byte) error {
   183  	init, nt, err := UnmarshalNegToken(b)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	if init {
   188  		return errors.New("bytes were not that of a NegTokenResp")
   189  	}
   190  	nResp := nt.(NegTokenResp)
   191  	n.MechListMIC = nResp.MechListMIC
   192  	n.NegState = nResp.NegState
   193  	n.ResponseToken = nResp.ResponseToken
   194  	n.SupportedMech = nResp.SupportedMech
   195  	return nil
   196  }
   197  
   198  // Verify a Resp/Targ negotiation token
   199  func (n *NegTokenResp) Verify() (bool, gssapi.Status) {
   200  	if n.SupportedMech.Equal(gssapi.OIDKRB5.OID()) || n.SupportedMech.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
   201  		if n.mechToken == nil && n.ResponseToken == nil {
   202  			return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
   203  		}
   204  		mt := new(KRB5Token)
   205  		mt.settings = n.settings
   206  		if n.mechToken == nil {
   207  			err := mt.Unmarshal(n.ResponseToken)
   208  			if err != nil {
   209  				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
   210  			}
   211  			n.mechToken = mt
   212  		} else {
   213  			var ok bool
   214  			mt, ok = n.mechToken.(*KRB5Token)
   215  			if !ok {
   216  				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
   217  			}
   218  		}
   219  		if mt == nil {
   220  			return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
   221  		}
   222  		// Verify the mechtoken
   223  		return mt.Verify()
   224  	}
   225  	return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
   226  }
   227  
   228  // State returns the negotiation state of the negotiation response.
   229  func (n *NegTokenResp) State() NegState {
   230  	return NegState(n.NegState)
   231  }
   232  
   233  // Context returns the SPNEGO context which will contain any verify user identity information.
   234  func (n *NegTokenResp) Context() context.Context {
   235  	if n.mechToken != nil {
   236  		mt, ok := n.mechToken.(*KRB5Token)
   237  		if !ok {
   238  			return nil
   239  		}
   240  		return mt.Context()
   241  	}
   242  	return nil
   243  }
   244  
   245  // UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp.
   246  //
   247  // The boolean indicates if the response is a NegTokenInit.
   248  // If error is nil and the boolean is false the response is a NegTokenResp.
   249  func UnmarshalNegToken(b []byte) (bool, interface{}, error) {
   250  	var a asn1.RawValue
   251  	_, err := asn1.Unmarshal(b, &a)
   252  	if err != nil {
   253  		return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err)
   254  	}
   255  	switch a.Tag {
   256  	case 0:
   257  		var n marshalNegTokenInit
   258  		_, err = asn1.Unmarshal(a.Bytes, &n)
   259  		if err != nil {
   260  			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err)
   261  		}
   262  		nt := NegTokenInit{
   263  			MechTypes:      n.MechTypes,
   264  			ReqFlags:       n.ReqFlags,
   265  			MechTokenBytes: n.MechTokenBytes,
   266  			MechListMIC:    n.MechListMIC,
   267  		}
   268  		return true, nt, nil
   269  	case 1:
   270  		var n marshalNegTokenResp
   271  		_, err = asn1.Unmarshal(a.Bytes, &n)
   272  		if err != nil {
   273  			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err)
   274  		}
   275  		nt := NegTokenResp{
   276  			NegState:      n.NegState,
   277  			SupportedMech: n.SupportedMech,
   278  			ResponseToken: n.ResponseToken,
   279  			MechListMIC:   n.MechListMIC,
   280  		}
   281  		return false, nt, nil
   282  	default:
   283  		return false, nil, errors.New("unknown choice type for NegotiationToken")
   284  	}
   285  
   286  }
   287  
   288  // NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5
   289  func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) {
   290  	mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{})
   291  	if err != nil {
   292  		return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err)
   293  	}
   294  	mtb, err := mt.Marshal()
   295  	if err != nil {
   296  		return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err)
   297  	}
   298  	return NegTokenInit{
   299  		MechTypes:      []asn1.ObjectIdentifier{gssapi.OIDKRB5.OID()},
   300  		MechTokenBytes: mtb,
   301  	}, nil
   302  }