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

     1  package spnego
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/jcmturner/gofork/encoding/asn1"
    11  	"github.com/jcmturner/gokrb5/v8/asn1tools"
    12  	"github.com/jcmturner/gokrb5/v8/client"
    13  	"github.com/jcmturner/gokrb5/v8/credentials"
    14  	"github.com/jcmturner/gokrb5/v8/gssapi"
    15  	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
    16  	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
    17  	"github.com/jcmturner/gokrb5/v8/krberror"
    18  	"github.com/jcmturner/gokrb5/v8/messages"
    19  	"github.com/jcmturner/gokrb5/v8/service"
    20  	"github.com/jcmturner/gokrb5/v8/types"
    21  )
    22  
    23  // GSSAPI KRB5 MechToken IDs.
    24  const (
    25  	TOK_ID_KRB_AP_REQ = "0100"
    26  	TOK_ID_KRB_AP_REP = "0200"
    27  	TOK_ID_KRB_ERROR  = "0300"
    28  )
    29  
    30  // KRB5Token context token implementation for GSSAPI.
    31  type KRB5Token struct {
    32  	OID      asn1.ObjectIdentifier
    33  	tokID    []byte
    34  	APReq    messages.APReq
    35  	APRep    messages.APRep
    36  	KRBError messages.KRBError
    37  	settings *service.Settings
    38  	context  context.Context
    39  }
    40  
    41  // Marshal a KRB5Token into a slice of bytes.
    42  func (m *KRB5Token) Marshal() ([]byte, error) {
    43  	// Create the header
    44  	b, _ := asn1.Marshal(m.OID)
    45  	b = append(b, m.tokID...)
    46  	var tb []byte
    47  	var err error
    48  	switch hex.EncodeToString(m.tokID) {
    49  	case TOK_ID_KRB_AP_REQ:
    50  		tb, err = m.APReq.Marshal()
    51  		if err != nil {
    52  			return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err)
    53  		}
    54  	case TOK_ID_KRB_AP_REP:
    55  		return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5")
    56  	case TOK_ID_KRB_ERROR:
    57  		return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5")
    58  	}
    59  	if err != nil {
    60  		return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err)
    61  	}
    62  	b = append(b, tb...)
    63  	return asn1tools.AddASNAppTag(b, 0), nil
    64  }
    65  
    66  // Unmarshal a KRB5Token.
    67  func (m *KRB5Token) Unmarshal(b []byte) error {
    68  	var oid asn1.ObjectIdentifier
    69  	r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
    70  	if err != nil {
    71  		return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err)
    72  	}
    73  	if !oid.Equal(gssapi.OIDKRB5.OID()) {
    74  		return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OIDKRB5.OID().String())
    75  	}
    76  	m.OID = oid
    77  	if len(r) < 2 {
    78  		return fmt.Errorf("krb5token too short")
    79  	}
    80  	m.tokID = r[0:2]
    81  	switch hex.EncodeToString(m.tokID) {
    82  	case TOK_ID_KRB_AP_REQ:
    83  		var a messages.APReq
    84  		err = a.Unmarshal(r[2:])
    85  		if err != nil {
    86  			return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err)
    87  		}
    88  		m.APReq = a
    89  	case TOK_ID_KRB_AP_REP:
    90  		var a messages.APRep
    91  		err = a.Unmarshal(r[2:])
    92  		if err != nil {
    93  			return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err)
    94  		}
    95  		m.APRep = a
    96  	case TOK_ID_KRB_ERROR:
    97  		var a messages.KRBError
    98  		err = a.Unmarshal(r[2:])
    99  		if err != nil {
   100  			return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err)
   101  		}
   102  		m.KRBError = a
   103  	}
   104  	return nil
   105  }
   106  
   107  // Verify a KRB5Token.
   108  func (m *KRB5Token) Verify() (bool, gssapi.Status) {
   109  	switch hex.EncodeToString(m.tokID) {
   110  	case TOK_ID_KRB_AP_REQ:
   111  		ok, creds, err := service.VerifyAPREQ(&m.APReq, m.settings)
   112  		if err != nil {
   113  			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
   114  		}
   115  		if !ok {
   116  			return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"}
   117  		}
   118  		m.context = context.Background()
   119  		m.context = context.WithValue(m.context, ctxCredentials, creds)
   120  		return true, gssapi.Status{Code: gssapi.StatusComplete}
   121  	case TOK_ID_KRB_AP_REP:
   122  		// Client side
   123  		// TODO how to verify the AP_REP - not yet implemented
   124  		return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"}
   125  	case TOK_ID_KRB_ERROR:
   126  		if m.KRBError.MsgType != msgtype.KRB_ERROR {
   127  			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"}
   128  		}
   129  		return true, gssapi.Status{Code: gssapi.StatusUnavailable}
   130  	}
   131  	return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"}
   132  }
   133  
   134  // IsAPReq tests if the MechToken contains an AP_REQ.
   135  func (m *KRB5Token) IsAPReq() bool {
   136  	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ {
   137  		return true
   138  	}
   139  	return false
   140  }
   141  
   142  // IsAPRep tests if the MechToken contains an AP_REP.
   143  func (m *KRB5Token) IsAPRep() bool {
   144  	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP {
   145  		return true
   146  	}
   147  	return false
   148  }
   149  
   150  // IsKRBError tests if the MechToken contains an KRB_ERROR.
   151  func (m *KRB5Token) IsKRBError() bool {
   152  	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR {
   153  		return true
   154  	}
   155  	return false
   156  }
   157  
   158  // Context returns the KRB5 token's context which will contain any verify user identity information.
   159  func (m *KRB5Token) Context() context.Context {
   160  	return m.context
   161  }
   162  
   163  // NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ
   164  func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) {
   165  	// TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client.
   166  	var m KRB5Token
   167  	m.OID = gssapi.OIDKRB5.OID()
   168  	tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
   169  	m.tokID = tb
   170  
   171  	auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags)
   172  	if err != nil {
   173  		return m, err
   174  	}
   175  	APReq, err := messages.NewAPReq(
   176  		tkt,
   177  		sessionKey,
   178  		auth,
   179  	)
   180  	if err != nil {
   181  		return m, err
   182  	}
   183  	for _, o := range APOptions {
   184  		types.SetFlag(&APReq.APOptions, o)
   185  	}
   186  	m.APReq = APReq
   187  	return m, nil
   188  }
   189  
   190  // krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken
   191  func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) {
   192  	//RFC 4121 Section 4.1.1
   193  	auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
   194  	if err != nil {
   195  		return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
   196  	}
   197  	auth.Cksum = types.Checksum{
   198  		CksumType: chksumtype.GSSAPI,
   199  		Checksum:  newAuthenticatorChksum(flags),
   200  	}
   201  	return auth, nil
   202  }
   203  
   204  // Create new authenticator checksum for kerberos MechToken
   205  func newAuthenticatorChksum(flags []int) []byte {
   206  	a := make([]byte, 24)
   207  	binary.LittleEndian.PutUint32(a[:4], 16)
   208  	for _, i := range flags {
   209  		if i == gssapi.ContextFlagDeleg {
   210  			x := make([]byte, 28-len(a))
   211  			a = append(a, x...)
   212  		}
   213  		f := binary.LittleEndian.Uint32(a[20:24])
   214  		f |= uint32(i)
   215  		binary.LittleEndian.PutUint32(a[20:24], f)
   216  	}
   217  	return a
   218  }