package messages // Reference: https://www.ietf.org/rfc/rfc4120.txt // Section: 5.4.1 import ( "crypto/rand" "fmt" "math" "math/big" "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/iana/patype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/types" ) type marshalKDCReq struct { PVNO int `asn1:"explicit,tag:1"` MsgType int `asn1:"explicit,tag:2"` PAData types.PADataSequence `asn1:"explicit,optional,tag:3"` ReqBody asn1.RawValue `asn1:"explicit,tag:4"` } // KDCReqFields represents the KRB_KDC_REQ fields. type KDCReqFields struct { PVNO int MsgType int PAData types.PADataSequence ReqBody KDCReqBody Renewal bool } // ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type ASReq struct { KDCReqFields } // TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1. type TGSReq struct { KDCReqFields } type marshalKDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"` } // KDCReqBody implements the KRB_KDC_REQ request body. type KDCReqBody struct { KDCOptions asn1.BitString `asn1:"explicit,tag:0"` CName types.PrincipalName `asn1:"explicit,optional,tag:1"` Realm string `asn1:"generalstring,explicit,tag:2"` SName types.PrincipalName `asn1:"explicit,optional,tag:3"` From time.Time `asn1:"generalized,explicit,optional,tag:4"` Till time.Time `asn1:"generalized,explicit,tag:5"` RTime time.Time `asn1:"generalized,explicit,optional,tag:6"` Nonce int `asn1:"explicit,tag:7"` EType []int32 `asn1:"explicit,tag:8"` Addresses []types.HostAddress `asn1:"explicit,optional,tag:9"` EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"` AdditionalTickets []Ticket `asn1:"explicit,optional,tag:11"` } // NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request. func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", realm}, } return NewASReq(realm, c, cname, sname) } // NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request. func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) { sname := types.PrincipalName{ NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"kadmin", "changepw"}, } return NewASReq(realm, c, cname, sname) } // NewASReq generates a new KRB_AS_REQ struct for a given SNAME. func NewASReq(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return ASReq{}, err } t := time.Now().UTC() // Copy the default options to make this thread safe kopts := types.NewKrbFlags() copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes) kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength a := ASReq{ KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AS_REQ, PAData: types.PADataSequence{}, ReqBody: KDCReqBody{ KDCOptions: kopts, Realm: realm, CName: cname, SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTktEnctypeIDs, }, }, } if c.LibDefaults.Forwardable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime != 0 { types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable) a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return a, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) a.ReqBody.Addresses = ha } return a, nil } // NewTGSReq generates a new KRB_TGS_REQ struct. func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } err = a.setPAData(tgt, sessionKey) return a, err } // NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7) func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) { a, err := tgsReq(cname, sname, kdcRealm, renewal, c) if err != nil { return a, err } a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT} types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey) err = a.setPAData(clientTGT, sessionKey) return a, err } // tgsReq populates the fields for a TGS_REQ func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) { nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32)) if err != nil { return TGSReq{}, err } t := time.Now().UTC() k := KDCReqFields{ PVNO: iana.PVNO, MsgType: msgtype.KRB_TGS_REQ, ReqBody: KDCReqBody{ KDCOptions: types.NewKrbFlags(), Realm: kdcRealm, CName: cname, // Add the CName to make validation of the reply easier SName: sname, Till: t.Add(c.LibDefaults.TicketLifetime), Nonce: int(nonce.Int64()), EType: c.LibDefaults.DefaultTGSEnctypeIDs, }, Renewal: renewal, } if c.LibDefaults.Forwardable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable) } if c.LibDefaults.Canonicalize { types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize) } if c.LibDefaults.Proxiable { types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable) } if c.LibDefaults.RenewLifetime > time.Duration(0) { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime) } if !c.LibDefaults.NoAddresses { ha, err := types.LocalHostAddresses() if err != nil { return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err) } ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...) k.ReqBody.Addresses = ha } if renewal { types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew) types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable) } return TGSReq{ k, }, nil } func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error { // Marshal the request and calculate checksum b, err := k.ReqBody.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body") } etype, err := crypto.GetEtype(sessionKey.KeyType) if err != nil { return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator") } cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM) if err != nil { return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash") } // Form PAData for TGS_REQ // Create authenticator auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator") } auth.Cksum = types.Checksum{ CksumType: etype.GetHashID(), Checksum: cb, } // Create AP_REQ apReq, err := NewAPReq(tgt, sessionKey, auth) if err != nil { return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ") } apb, err := apReq.Marshal() if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data") } k.PAData = types.PADataSequence{ types.PAData{ PADataType: patype.PA_TGS_REQ, PADataValue: apb, }, } return nil } // Unmarshal bytes b into the ASReq struct. func (k *ASReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ") } expectedMsgType := msgtype.KRB_AS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the TGSReq struct. func (k *TGSReq) Unmarshal(b []byte) error { var m marshalKDCReq _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ)) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ") } expectedMsgType := msgtype.KRB_TGS_REQ if m.MsgType != expectedMsgType { return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType) } var reqb KDCReqBody err = reqb.Unmarshal(m.ReqBody.Bytes) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body") } k.MsgType = m.MsgType k.PAData = m.PAData k.PVNO = m.PVNO k.ReqBody = reqb return nil } // Unmarshal bytes b into the KRB_KDC_REQ body struct. func (k *KDCReqBody) Unmarshal(b []byte) error { var m marshalKDCReqBody _, err := asn1.Unmarshal(b, &m) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body") } k.KDCOptions = m.KDCOptions if len(k.KDCOptions.Bytes) < 4 { tb := make([]byte, 4-len(k.KDCOptions.Bytes)) k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...) k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8 } k.CName = m.CName k.Realm = m.Realm k.SName = m.SName k.From = m.From k.Till = m.Till k.RTime = m.RTime k.Nonce = m.Nonce k.EType = m.EType k.Addresses = m.Addresses k.EncAuthData = m.EncAuthData if len(m.AdditionalTickets.Bytes) > 0 { k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets) if err != nil { return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets") } } return nil } // Marshal ASReq struct. func (k *ASReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ) return mk, nil } // Marshal TGSReq struct. func (k *TGSReq) Marshal() ([]byte, error) { m := marshalKDCReq{ PVNO: k.PVNO, MsgType: k.MsgType, PAData: k.PAData, } b, err := k.ReqBody.Marshal() if err != nil { var mk []byte return mk, err } m.ReqBody = asn1.RawValue{ Class: asn1.ClassContextSpecific, IsCompound: true, Tag: 4, Bytes: b, } mk, err := asn1.Marshal(m) if err != nil { return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ") } mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ) return mk, nil } // Marshal KRB_KDC_REQ body struct. func (k *KDCReqBody) Marshal() ([]byte, error) { var b []byte m := marshalKDCReqBody{ KDCOptions: k.KDCOptions, CName: k.CName, Realm: k.Realm, SName: k.SName, From: k.From, Till: k.Till, RTime: k.RTime, Nonce: k.Nonce, EType: k.EType, Addresses: k.Addresses, EncAuthData: k.EncAuthData, } rawtkts, err := MarshalTicketSequence(k.AdditionalTickets) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets") } //The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody rawtkts.Tag = 11 if len(rawtkts.Bytes) > 0 { m.AdditionalTickets = rawtkts } b, err = asn1.Marshal(m) if err != nil { return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body") } return b, nil }