123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- package client
- import (
- "github.com/jcmturner/gokrb5/v8/crypto"
- "github.com/jcmturner/gokrb5/v8/crypto/etype"
- "github.com/jcmturner/gokrb5/v8/iana/errorcode"
- "github.com/jcmturner/gokrb5/v8/iana/keyusage"
- "github.com/jcmturner/gokrb5/v8/iana/patype"
- "github.com/jcmturner/gokrb5/v8/krberror"
- "github.com/jcmturner/gokrb5/v8/messages"
- "github.com/jcmturner/gokrb5/v8/types"
- )
- // ASExchange performs an AS exchange for the client to retrieve a TGT.
- func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
- if ok, err := cl.IsConfigured(); !ok {
- return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
- }
- // Set PAData if required
- err := setPAData(cl, nil, &ASReq)
- if err != nil {
- return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
- }
- b, err := ASReq.Marshal()
- if err != nil {
- return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
- }
- var ASRep messages.ASRep
- rb, err := cl.sendToKDC(b, realm)
- if err != nil {
- if e, ok := err.(messages.KRBError); ok {
- switch e.ErrorCode {
- case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
- // From now on assume this client will need to do this pre-auth and set the PAData
- cl.settings.assumePreAuthentication = true
- err = setPAData(cl, &e, &ASReq)
- if err != nil {
- return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
- }
- b, err := ASReq.Marshal()
- if err != nil {
- return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
- }
- rb, err = cl.sendToKDC(b, realm)
- if err != nil {
- if _, ok := err.(messages.KRBError); ok {
- return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
- }
- return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
- }
- case errorcode.KDC_ERR_WRONG_REALM:
- // Client referral https://tools.ietf.org/html/rfc6806.html#section-7
- if referral > 5 {
- return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
- }
- referral++
- return cl.ASExchange(e.CRealm, ASReq, referral)
- default:
- return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
- }
- } else {
- return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
- }
- }
- err = ASRep.Unmarshal(rb)
- if err != nil {
- return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
- }
- if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
- return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
- }
- return ASRep, nil
- }
- // setPAData adds pre-authentication data to the AS_REQ.
- func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
- if !cl.settings.DisablePAFXFAST() {
- pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
- ASReq.PAData = append(ASReq.PAData, pa)
- }
- if cl.settings.AssumePreAuthentication() {
- // Identify the etype to use to encrypt the PA Data
- var et etype.EType
- var err error
- var key types.EncryptionKey
- var kvno int
- if krberr == nil {
- // This is not in response to an error from the KDC. It is preemptive or renewal
- // There is no KRB Error that tells us the etype to use
- etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
- if etn == 0 {
- etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
- }
- et, err = crypto.GetEtype(etn)
- if err != nil {
- return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
- }
- key, kvno, err = cl.Key(et, 0, nil)
- if err != nil {
- return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
- }
- } else {
- // Get the etype to use from the PA data in the KRBError e-data
- et, err = preAuthEType(krberr)
- if err != nil {
- return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
- }
- cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
- key, kvno, err = cl.Key(et, 0, krberr)
- if err != nil {
- return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
- }
- }
- // Generate the PA data
- paTSb, err := types.GetPAEncTSEncAsnMarshalled()
- if err != nil {
- return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
- }
- paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno)
- if err != nil {
- return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
- }
- pb, err := paEncTS.Marshal()
- if err != nil {
- return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
- }
- pa := types.PAData{
- PADataType: patype.PA_ENC_TIMESTAMP,
- PADataValue: pb,
- }
- // Look for and delete any exiting patype.PA_ENC_TIMESTAMP
- for i, pa := range ASReq.PAData {
- if pa.PADataType == patype.PA_ENC_TIMESTAMP {
- ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
- ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
- }
- }
- ASReq.PAData = append(ASReq.PAData, pa)
- }
- return nil
- }
- // preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
- func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
- //RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO.
- var etypeID int32
- var pas types.PADataSequence
- e := pas.Unmarshal(krberr.EData)
- if e != nil {
- err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data")
- return
- }
- Loop:
- for _, pa := range pas {
- switch pa.PADataType {
- case patype.PA_ETYPE_INFO2:
- info, e := pa.GetETypeInfo2()
- if e != nil {
- err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data")
- return
- }
- etypeID = info[0].EType
- break Loop
- case patype.PA_ETYPE_INFO:
- info, e := pa.GetETypeInfo()
- if e != nil {
- err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data")
- return
- }
- etypeID = info[0].EType
- }
- }
- etype, e = crypto.GetEtype(etypeID)
- if e != nil {
- err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype")
- return
- }
- return etype, nil
- }
|