package spnego import ( "context" "encoding/binary" "encoding/hex" "errors" "fmt" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/chksumtype" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/service" "github.com/jcmturner/gokrb5/v8/types" ) // GSSAPI KRB5 MechToken IDs. const ( TOK_ID_KRB_AP_REQ = "0100" TOK_ID_KRB_AP_REP = "0200" TOK_ID_KRB_ERROR = "0300" ) // KRB5Token context token implementation for GSSAPI. type KRB5Token struct { OID asn1.ObjectIdentifier tokID []byte APReq messages.APReq APRep messages.APRep KRBError messages.KRBError settings *service.Settings context context.Context } // Marshal a KRB5Token into a slice of bytes. func (m *KRB5Token) Marshal() ([]byte, error) { // Create the header b, _ := asn1.Marshal(m.OID) b = append(b, m.tokID...) var tb []byte var err error switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: tb, err = m.APReq.Marshal() if err != nil { return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err) } case TOK_ID_KRB_AP_REP: return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5") case TOK_ID_KRB_ERROR: return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5") } if err != nil { return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err) } b = append(b, tb...) return asn1tools.AddASNAppTag(b, 0), nil } // Unmarshal a KRB5Token. func (m *KRB5Token) Unmarshal(b []byte) error { var oid asn1.ObjectIdentifier r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err) } if !oid.Equal(gssapi.OIDKRB5.OID()) { return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OIDKRB5.OID().String()) } m.OID = oid if len(r) < 2 { return fmt.Errorf("krb5token too short") } m.tokID = r[0:2] switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: var a messages.APReq err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err) } m.APReq = a case TOK_ID_KRB_AP_REP: var a messages.APRep err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err) } m.APRep = a case TOK_ID_KRB_ERROR: var a messages.KRBError err = a.Unmarshal(r[2:]) if err != nil { return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err) } m.KRBError = a } return nil } // Verify a KRB5Token. func (m *KRB5Token) Verify() (bool, gssapi.Status) { switch hex.EncodeToString(m.tokID) { case TOK_ID_KRB_AP_REQ: ok, creds, err := service.VerifyAPREQ(&m.APReq, m.settings) if err != nil { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()} } if !ok { return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"} } m.context = context.Background() m.context = context.WithValue(m.context, ctxCredentials, creds) return true, gssapi.Status{Code: gssapi.StatusComplete} case TOK_ID_KRB_AP_REP: // Client side // TODO how to verify the AP_REP - not yet implemented return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"} case TOK_ID_KRB_ERROR: if m.KRBError.MsgType != msgtype.KRB_ERROR { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"} } return true, gssapi.Status{Code: gssapi.StatusUnavailable} } return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"} } // IsAPReq tests if the MechToken contains an AP_REQ. func (m *KRB5Token) IsAPReq() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ { return true } return false } // IsAPRep tests if the MechToken contains an AP_REP. func (m *KRB5Token) IsAPRep() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP { return true } return false } // IsKRBError tests if the MechToken contains an KRB_ERROR. func (m *KRB5Token) IsKRBError() bool { if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR { return true } return false } // Context returns the KRB5 token's context which will contain any verify user identity information. func (m *KRB5Token) Context() context.Context { return m.context } // NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) { // TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client. var m KRB5Token m.OID = gssapi.OIDKRB5.OID() tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ) m.tokID = tb auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags) if err != nil { return m, err } APReq, err := messages.NewAPReq( tkt, sessionKey, auth, ) if err != nil { return m, err } for _, o := range APOptions { types.SetFlag(&APReq.APOptions, o) } m.APReq = APReq return m, nil } // krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) { //RFC 4121 Section 4.1.1 auth, err := types.NewAuthenticator(creds.Domain(), creds.CName()) if err != nil { return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: chksumtype.GSSAPI, Checksum: newAuthenticatorChksum(flags), } return auth, nil } // Create new authenticator checksum for kerberos MechToken func newAuthenticatorChksum(flags []int) []byte { a := make([]byte, 24) binary.LittleEndian.PutUint32(a[:4], 16) for _, i := range flags { if i == gssapi.ContextFlagDeleg { x := make([]byte, 28-len(a)) a = append(a, x...) } f := binary.LittleEndian.Uint32(a[20:24]) f |= uint32(i) binary.LittleEndian.PutUint32(a[20:24], f) } return a }