github.com/jcmturner/gokrb5/v8@v8.4.4/client/ASExchange.go (about)

     1  package client
     2  
     3  import (
     4  	"github.com/jcmturner/gokrb5/v8/crypto"
     5  	"github.com/jcmturner/gokrb5/v8/crypto/etype"
     6  	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
     7  	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
     8  	"github.com/jcmturner/gokrb5/v8/iana/patype"
     9  	"github.com/jcmturner/gokrb5/v8/krberror"
    10  	"github.com/jcmturner/gokrb5/v8/messages"
    11  	"github.com/jcmturner/gokrb5/v8/types"
    12  )
    13  
    14  // ASExchange performs an AS exchange for the client to retrieve a TGT.
    15  func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
    16  	if ok, err := cl.IsConfigured(); !ok {
    17  		return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
    18  	}
    19  
    20  	// Set PAData if required
    21  	err := setPAData(cl, nil, &ASReq)
    22  	if err != nil {
    23  		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
    24  	}
    25  
    26  	b, err := ASReq.Marshal()
    27  	if err != nil {
    28  		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
    29  	}
    30  	var ASRep messages.ASRep
    31  
    32  	rb, err := cl.sendToKDC(b, realm)
    33  	if err != nil {
    34  		if e, ok := err.(messages.KRBError); ok {
    35  			switch e.ErrorCode {
    36  			case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
    37  				// From now on assume this client will need to do this pre-auth and set the PAData
    38  				cl.settings.assumePreAuthentication = true
    39  				err = setPAData(cl, &e, &ASReq)
    40  				if err != nil {
    41  					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
    42  				}
    43  				b, err := ASReq.Marshal()
    44  				if err != nil {
    45  					return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
    46  				}
    47  				rb, err = cl.sendToKDC(b, realm)
    48  				if err != nil {
    49  					if _, ok := err.(messages.KRBError); ok {
    50  						return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
    51  					}
    52  					return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
    53  				}
    54  			case errorcode.KDC_ERR_WRONG_REALM:
    55  				// Client referral https://tools.ietf.org/html/rfc6806.html#section-7
    56  				if referral > 5 {
    57  					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
    58  				}
    59  				referral++
    60  				return cl.ASExchange(e.CRealm, ASReq, referral)
    61  			default:
    62  				return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
    63  			}
    64  		} else {
    65  			return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
    66  		}
    67  	}
    68  	err = ASRep.Unmarshal(rb)
    69  	if err != nil {
    70  		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
    71  	}
    72  	if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
    73  		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
    74  	}
    75  	return ASRep, nil
    76  }
    77  
    78  // setPAData adds pre-authentication data to the AS_REQ.
    79  func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
    80  	if !cl.settings.DisablePAFXFAST() {
    81  		pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
    82  		ASReq.PAData = append(ASReq.PAData, pa)
    83  	}
    84  	if cl.settings.AssumePreAuthentication() {
    85  		// Identify the etype to use to encrypt the PA Data
    86  		var et etype.EType
    87  		var err error
    88  		var key types.EncryptionKey
    89  		var kvno int
    90  		if krberr == nil {
    91  			// This is not in response to an error from the KDC. It is preemptive or renewal
    92  			// There is no KRB Error that tells us the etype to use
    93  			etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
    94  			if etn == 0 {
    95  				etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
    96  			}
    97  			et, err = crypto.GetEtype(etn)
    98  			if err != nil {
    99  				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
   100  			}
   101  			key, kvno, err = cl.Key(et, 0, nil)
   102  			if err != nil {
   103  				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
   104  			}
   105  		} else {
   106  			// Get the etype to use from the PA data in the KRBError e-data
   107  			et, err = preAuthEType(krberr)
   108  			if err != nil {
   109  				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
   110  			}
   111  			cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
   112  			key, kvno, err = cl.Key(et, 0, krberr)
   113  			if err != nil {
   114  				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
   115  			}
   116  		}
   117  		// Generate the PA data
   118  		paTSb, err := types.GetPAEncTSEncAsnMarshalled()
   119  		if err != nil {
   120  			return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
   121  		}
   122  		paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno)
   123  		if err != nil {
   124  			return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
   125  		}
   126  		pb, err := paEncTS.Marshal()
   127  		if err != nil {
   128  			return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
   129  		}
   130  		pa := types.PAData{
   131  			PADataType:  patype.PA_ENC_TIMESTAMP,
   132  			PADataValue: pb,
   133  		}
   134  		// Look for and delete any exiting patype.PA_ENC_TIMESTAMP
   135  		for i, pa := range ASReq.PAData {
   136  			if pa.PADataType == patype.PA_ENC_TIMESTAMP {
   137  				ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
   138  				ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
   139  			}
   140  		}
   141  		ASReq.PAData = append(ASReq.PAData, pa)
   142  	}
   143  	return nil
   144  }
   145  
   146  // preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
   147  func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
   148  	//RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO.
   149  	var etypeID int32
   150  	var pas types.PADataSequence
   151  	e := pas.Unmarshal(krberr.EData)
   152  	if e != nil {
   153  		err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data")
   154  		return
   155  	}
   156  Loop:
   157  	for _, pa := range pas {
   158  		switch pa.PADataType {
   159  		case patype.PA_ETYPE_INFO2:
   160  			info, e := pa.GetETypeInfo2()
   161  			if e != nil {
   162  				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data")
   163  				return
   164  			}
   165  			etypeID = info[0].EType
   166  			break Loop
   167  		case patype.PA_ETYPE_INFO:
   168  			info, e := pa.GetETypeInfo()
   169  			if e != nil {
   170  				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data")
   171  				return
   172  			}
   173  			etypeID = info[0].EType
   174  		}
   175  	}
   176  	etype, e = crypto.GetEtype(etypeID)
   177  	if e != nil {
   178  		err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype")
   179  		return
   180  	}
   181  	return etype, nil
   182  }