Update module lib/pq to v1.6.0 (#572)
Update module lib/pq to v1.6.0 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/572
This commit is contained in:
182
vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
generated
vendored
Normal file
182
vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
103
vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
generated
vendored
Normal file
103
vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/jcmturner/gokrb5/v8/iana/flags"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/nametype"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN.
|
||||
func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
|
||||
tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ")
|
||||
}
|
||||
return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0)
|
||||
}
|
||||
|
||||
// TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP.
|
||||
// Referrals are automatically handled.
|
||||
// The client's cache is updated with the ticket received.
|
||||
func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
|
||||
var tgsRep messages.TGSRep
|
||||
b, err := tgsReq.Marshal()
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ")
|
||||
}
|
||||
r, err := cl.sendToKDC(b, kdcRealm)
|
||||
if err != nil {
|
||||
if _, ok := err.(messages.KRBError); ok {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString())
|
||||
}
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
|
||||
}
|
||||
err = tgsRep.Unmarshal(r)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
|
||||
}
|
||||
err = tgsRep.DecryptEncPart(sessionKey)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
|
||||
}
|
||||
if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
|
||||
}
|
||||
|
||||
if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
|
||||
if referral > 5 {
|
||||
return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded")
|
||||
}
|
||||
// Server referral https://tools.ietf.org/html/rfc6806.html#section-8
|
||||
// The TGS Rep contains a TGT for another domain as the service resides in that domain.
|
||||
cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
|
||||
referral++
|
||||
if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 {
|
||||
tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
}
|
||||
tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal)
|
||||
if err != nil {
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
|
||||
}
|
||||
cl.cache.addEntry(
|
||||
tgsRep.Ticket,
|
||||
tgsRep.DecryptedEncPart.AuthTime,
|
||||
tgsRep.DecryptedEncPart.StartTime,
|
||||
tgsRep.DecryptedEncPart.EndTime,
|
||||
tgsRep.DecryptedEncPart.RenewTill,
|
||||
tgsRep.DecryptedEncPart.Key,
|
||||
)
|
||||
cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime)
|
||||
return tgsReq, tgsRep, err
|
||||
}
|
||||
|
||||
// GetServiceTicket makes a request to get a service ticket for the SPN specified
|
||||
// SPN format: <SERVICE>/<FQDN> Eg. HTTP/www.example.com
|
||||
// The ticket will be added to the client's ticket cache
|
||||
func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) {
|
||||
var tkt messages.Ticket
|
||||
var skey types.EncryptionKey
|
||||
if tkt, skey, ok := cl.GetCachedTicket(spn); ok {
|
||||
// Already a valid ticket in the cache
|
||||
return tkt, skey, nil
|
||||
}
|
||||
princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn)
|
||||
realm := cl.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1])
|
||||
|
||||
tgt, skey, err := cl.sessionTGT(realm)
|
||||
if err != nil {
|
||||
return tkt, skey, err
|
||||
}
|
||||
_, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false)
|
||||
if err != nil {
|
||||
return tkt, skey, err
|
||||
}
|
||||
return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil
|
||||
}
|
134
vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
generated
vendored
Normal file
134
vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// Cache for service tickets held by the client.
|
||||
type Cache struct {
|
||||
Entries map[string]CacheEntry
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// CacheEntry holds details for a cache entry.
|
||||
type CacheEntry struct {
|
||||
SPN string
|
||||
Ticket messages.Ticket `json:"-"`
|
||||
AuthTime time.Time
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
RenewTill time.Time
|
||||
SessionKey types.EncryptionKey `json:"-"`
|
||||
}
|
||||
|
||||
// NewCache creates a new client ticket cache instance.
|
||||
func NewCache() *Cache {
|
||||
return &Cache{
|
||||
Entries: map[string]CacheEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
// getEntry returns a cache entry that matches the SPN.
|
||||
func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
e, ok := (*c).Entries[spn]
|
||||
return e, ok
|
||||
}
|
||||
|
||||
// JSON returns information about the cached service tickets in a JSON format.
|
||||
func (c *Cache) JSON() (string, error) {
|
||||
c.mux.RLock()
|
||||
defer c.mux.RUnlock()
|
||||
var es []CacheEntry
|
||||
keys := make([]string, 0, len(c.Entries))
|
||||
for k := range c.Entries {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
es = append(es, c.Entries[k])
|
||||
}
|
||||
b, err := json.MarshalIndent(&es, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// addEntry adds a ticket to the cache.
|
||||
func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
|
||||
spn := tkt.SName.PrincipalNameString()
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
(*c).Entries[spn] = CacheEntry{
|
||||
SPN: spn,
|
||||
Ticket: tkt,
|
||||
AuthTime: authTime,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
RenewTill: renewTill,
|
||||
SessionKey: sessionKey,
|
||||
}
|
||||
return c.Entries[spn]
|
||||
}
|
||||
|
||||
// clear deletes all the cache entries
|
||||
func (c *Cache) clear() {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
for k := range c.Entries {
|
||||
delete(c.Entries, k)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveEntry removes the cache entry for the defined SPN.
|
||||
func (c *Cache) RemoveEntry(spn string) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
delete(c.Entries, spn)
|
||||
}
|
||||
|
||||
// GetCachedTicket returns a ticket from the cache for the SPN.
|
||||
// Only a ticket that is currently valid will be returned.
|
||||
func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
|
||||
if e, ok := cl.cache.getEntry(spn); ok {
|
||||
//If within time window of ticket return it
|
||||
if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
|
||||
cl.Log("ticket received from cache for %s", spn)
|
||||
return e.Ticket, e.SessionKey, true
|
||||
} else if time.Now().UTC().Before(e.RenewTill) {
|
||||
e, err := cl.renewTicket(e)
|
||||
if err != nil {
|
||||
return e.Ticket, e.SessionKey, false
|
||||
}
|
||||
return e.Ticket, e.SessionKey, true
|
||||
}
|
||||
}
|
||||
var tkt messages.Ticket
|
||||
var key types.EncryptionKey
|
||||
return tkt, key, false
|
||||
}
|
||||
|
||||
// renewTicket renews a cache entry ticket.
|
||||
// To renew from outside the client package use GetCachedTicket
|
||||
func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
|
||||
spn := e.Ticket.SName
|
||||
_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
|
||||
if !ok {
|
||||
return e, errors.New("ticket was not added to cache")
|
||||
}
|
||||
cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime)
|
||||
return e, nil
|
||||
}
|
329
vendor/github.com/jcmturner/gokrb5/v8/client/client.go
generated
vendored
Normal file
329
vendor/github.com/jcmturner/gokrb5/v8/client/client.go
generated
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
// Package client provides a client library and methods for Kerberos 5 authentication.
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/config"
|
||||
"github.com/jcmturner/gokrb5/v8/credentials"
|
||||
"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/nametype"
|
||||
"github.com/jcmturner/gokrb5/v8/keytab"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// Client side configuration and state.
|
||||
type Client struct {
|
||||
Credentials *credentials.Credentials
|
||||
Config *config.Config
|
||||
settings *Settings
|
||||
sessions *sessions
|
||||
cache *Cache
|
||||
}
|
||||
|
||||
// NewWithPassword creates a new client from a password credential.
|
||||
// Set the realm to empty string to use the default realm from config.
|
||||
func NewWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client {
|
||||
creds := credentials.New(username, realm)
|
||||
return &Client{
|
||||
Credentials: creds.WithPassword(password),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithKeytab creates a new client from a keytab credential.
|
||||
func NewWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client {
|
||||
creds := credentials.New(username, realm)
|
||||
return &Client{
|
||||
Credentials: creds.WithKeytab(kt),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewFromCCache create a client from a populated client cache.
|
||||
//
|
||||
// WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
|
||||
func NewFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) {
|
||||
cl := &Client{
|
||||
Credentials: c.GetClientCredentials(),
|
||||
Config: krb5conf,
|
||||
settings: NewSettings(settings...),
|
||||
sessions: &sessions{
|
||||
Entries: make(map[string]*session),
|
||||
},
|
||||
cache: NewCache(),
|
||||
}
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
|
||||
}
|
||||
cred, ok := c.GetEntry(spn)
|
||||
if !ok {
|
||||
return cl, errors.New("TGT not found in CCache")
|
||||
}
|
||||
var tgt messages.Ticket
|
||||
err := tgt.Unmarshal(cred.Ticket)
|
||||
if err != nil {
|
||||
return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
|
||||
}
|
||||
cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
|
||||
realm: c.DefaultPrincipal.Realm,
|
||||
authTime: cred.AuthTime,
|
||||
endTime: cred.EndTime,
|
||||
renewTill: cred.RenewTill,
|
||||
tgt: tgt,
|
||||
sessionKey: cred.Key,
|
||||
}
|
||||
for _, cred := range c.GetEntries() {
|
||||
var tkt messages.Ticket
|
||||
err = tkt.Unmarshal(cred.Ticket)
|
||||
if err != nil {
|
||||
return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
|
||||
}
|
||||
cl.cache.addEntry(
|
||||
tkt,
|
||||
cred.AuthTime,
|
||||
cred.StartTime,
|
||||
cred.EndTime,
|
||||
cred.RenewTill,
|
||||
cred.Key,
|
||||
)
|
||||
}
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// Key returns the client's encryption key for the specified encryption type and its kvno (kvno of zero will find latest).
|
||||
// The key can be retrieved either from the keytab or generated from the client's password.
|
||||
// If the client has both a keytab and a password defined the keytab is favoured as the source for the key
|
||||
// A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive
|
||||
// the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument.
|
||||
func (cl *Client) Key(etype etype.EType, kvno int, krberr *messages.KRBError) (types.EncryptionKey, int, error) {
|
||||
if cl.Credentials.HasKeytab() && etype != nil {
|
||||
return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), kvno, etype.GetETypeID())
|
||||
} else if cl.Credentials.HasPassword() {
|
||||
if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
|
||||
var pas types.PADataSequence
|
||||
err := pas.Unmarshal(krberr.EData)
|
||||
if err != nil {
|
||||
return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
|
||||
}
|
||||
key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
|
||||
return key, 0, err
|
||||
}
|
||||
key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})
|
||||
return key, 0, err
|
||||
}
|
||||
return types.EncryptionKey{}, 0, errors.New("credential has neither keytab or password to generate key")
|
||||
}
|
||||
|
||||
// IsConfigured indicates if the client has the values required set.
|
||||
func (cl *Client) IsConfigured() (bool, error) {
|
||||
if cl.Credentials.UserName() == "" {
|
||||
return false, errors.New("client does not have a username")
|
||||
}
|
||||
if cl.Credentials.Domain() == "" {
|
||||
return false, errors.New("client does not have a define realm")
|
||||
}
|
||||
// Client needs to have either a password, keytab or a session already (later when loading from CCache)
|
||||
if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
|
||||
authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || authTime.IsZero() {
|
||||
return false, errors.New("client has neither a keytab nor a password set and no session")
|
||||
}
|
||||
}
|
||||
if !cl.Config.LibDefaults.DNSLookupKDC {
|
||||
for _, r := range cl.Config.Realms {
|
||||
if r.Realm == cl.Credentials.Domain() {
|
||||
if len(r.KDC) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Login the client with the KDC via an AS exchange.
|
||||
func (cl *Client) Login() error {
|
||||
if ok, err := cl.IsConfigured(); !ok {
|
||||
return err
|
||||
}
|
||||
if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session")
|
||||
}
|
||||
if time.Now().UTC().After(endTime) {
|
||||
return krberror.New(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session")
|
||||
}
|
||||
// no credentials but there is a session with tgt already
|
||||
return nil
|
||||
}
|
||||
ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
|
||||
}
|
||||
ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT.
|
||||
func (cl *Client) AffirmLogin() error {
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || time.Now().UTC().After(endTime) {
|
||||
err := cl.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// realmLogin obtains or renews a TGT and establishes a session for the realm specified.
|
||||
func (cl *Client) realmLogin(realm string) error {
|
||||
if realm == cl.Credentials.Domain() {
|
||||
return cl.Login()
|
||||
}
|
||||
_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
|
||||
if err != nil || time.Now().UTC().After(endTime) {
|
||||
err := cl.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
|
||||
}
|
||||
}
|
||||
tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", realm},
|
||||
}
|
||||
|
||||
_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
|
||||
func (cl *Client) Destroy() {
|
||||
creds := credentials.New("", "")
|
||||
cl.sessions.destroy()
|
||||
cl.cache.clear()
|
||||
cl.Credentials = creds
|
||||
cl.Log("client destroyed")
|
||||
}
|
||||
|
||||
// Diagnostics runs a set of checks that the client is properly configured and writes details to the io.Writer provided.
|
||||
func (cl *Client) Diagnostics(w io.Writer) error {
|
||||
cl.Print(w)
|
||||
var errs []string
|
||||
if cl.Credentials.HasKeytab() {
|
||||
var loginRealmEncTypes []int32
|
||||
for _, e := range cl.Credentials.Keytab().Entries {
|
||||
if e.Principal.Realm == cl.Credentials.Realm() {
|
||||
loginRealmEncTypes = append(loginRealmEncTypes, e.Key.KeyType)
|
||||
}
|
||||
}
|
||||
for _, et := range cl.Config.LibDefaults.DefaultTktEnctypeIDs {
|
||||
var etInKt bool
|
||||
for _, val := range loginRealmEncTypes {
|
||||
if val == et {
|
||||
etInKt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !etInKt {
|
||||
errs = append(errs, fmt.Sprintf("default_tkt_enctypes specifies %d but this enctype is not available in the client's keytab", et))
|
||||
}
|
||||
}
|
||||
for _, et := range cl.Config.LibDefaults.PreferredPreauthTypes {
|
||||
var etInKt bool
|
||||
for _, val := range loginRealmEncTypes {
|
||||
if int(val) == et {
|
||||
etInKt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !etInKt {
|
||||
errs = append(errs, fmt.Sprintf("preferred_preauth_types specifies %d but this enctype is not available in the client's keytab", et))
|
||||
}
|
||||
}
|
||||
}
|
||||
udpCnt, udpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("error when resolving KDCs for UDP communication: %v", err))
|
||||
}
|
||||
if udpCnt < 1 {
|
||||
errs = append(errs, "no KDCs resolved for communication via UDP.")
|
||||
} else {
|
||||
b, _ := json.MarshalIndent(&udpKDC, "", " ")
|
||||
fmt.Fprintf(w, "UDP KDCs: %s\n", string(b))
|
||||
}
|
||||
tcpCnt, tcpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("error when resolving KDCs for TCP communication: %v", err))
|
||||
}
|
||||
if tcpCnt < 1 {
|
||||
errs = append(errs, "no KDCs resolved for communication via TCP.")
|
||||
} else {
|
||||
b, _ := json.MarshalIndent(&tcpKDC, "", " ")
|
||||
fmt.Fprintf(w, "TCP KDCs: %s\n", string(b))
|
||||
}
|
||||
|
||||
if errs == nil || len(errs) < 1 {
|
||||
return nil
|
||||
}
|
||||
err = fmt.Errorf(strings.Join(errs, "\n"))
|
||||
return err
|
||||
}
|
||||
|
||||
// Print writes the details of the client to the io.Writer provided.
|
||||
func (cl *Client) Print(w io.Writer) {
|
||||
c, _ := cl.Credentials.JSON()
|
||||
fmt.Fprintf(w, "Credentials:\n%s\n", c)
|
||||
|
||||
s, _ := cl.sessions.JSON()
|
||||
fmt.Fprintf(w, "TGT Sessions:\n%s\n", s)
|
||||
|
||||
c, _ = cl.cache.JSON()
|
||||
fmt.Fprintf(w, "Service ticket cache:\n%s\n", c)
|
||||
|
||||
s, _ = cl.settings.JSON()
|
||||
fmt.Fprintf(w, "Settings:\n%s\n", s)
|
||||
|
||||
j, _ := cl.Config.JSON()
|
||||
fmt.Fprintf(w, "Krb5 config:\n%s\n", j)
|
||||
|
||||
k, _ := cl.Credentials.Keytab().JSON()
|
||||
fmt.Fprintf(w, "Keytab:\n%s\n", k)
|
||||
}
|
213
vendor/github.com/jcmturner/gokrb5/v8/client/network.go
generated
vendored
Normal file
213
vendor/github.com/jcmturner/gokrb5/v8/client/network.go
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/iana/errorcode"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
)
|
||||
|
||||
// SendToKDC performs network actions to send data to the KDC.
|
||||
func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) {
|
||||
var rb []byte
|
||||
if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
|
||||
//1 means we should always use TCP
|
||||
rb, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
return rb, e
|
||||
}
|
||||
return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
|
||||
//Try UDP first, TCP second
|
||||
rb, errudp := cl.sendKDCUDP(realm, b)
|
||||
if errudp != nil {
|
||||
if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
|
||||
// Got a KRBError from KDC
|
||||
// If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
|
||||
return rb, e
|
||||
}
|
||||
// Try TCP
|
||||
r, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
// Got a KRBError
|
||||
return r, e
|
||||
}
|
||||
return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
|
||||
}
|
||||
rb = r
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
//Try TCP first, UDP second
|
||||
rb, errtcp := cl.sendKDCTCP(realm, b)
|
||||
if errtcp != nil {
|
||||
if e, ok := errtcp.(messages.KRBError); ok {
|
||||
// Got a KRBError from KDC so returning and not trying UDP.
|
||||
return rb, e
|
||||
}
|
||||
rb, errudp := cl.sendKDCUDP(realm, b)
|
||||
if errudp != nil {
|
||||
if e, ok := errudp.(messages.KRBError); ok {
|
||||
// Got a KRBError
|
||||
return rb, e
|
||||
}
|
||||
return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
|
||||
}
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
|
||||
// dialKDCTCP establishes a UDP connection to a KDC.
|
||||
func dialKDCUDP(count int, kdcs map[int]string) (*net.UDPConn, error) {
|
||||
i := 1
|
||||
for i <= count {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving KDC address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second)
|
||||
if err == nil {
|
||||
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// conn is guaranteed to be a UDPConn
|
||||
return conn.(*net.UDPConn), nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil, errors.New("error in getting a UDP connection to any of the KDCs")
|
||||
}
|
||||
|
||||
// dialKDCTCP establishes a TCP connection to a KDC.
|
||||
func dialKDCTCP(count int, kdcs map[int]string) (*net.TCPConn, error) {
|
||||
i := 1
|
||||
for i <= count {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving KDC address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second)
|
||||
if err == nil {
|
||||
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// conn is guaranteed to be a TCPConn
|
||||
return conn.(*net.TCPConn), nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil, errors.New("error in getting a TCP connection to any of the KDCs")
|
||||
}
|
||||
|
||||
// sendKDCUDP sends bytes to the KDC via UDP.
|
||||
func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
count, kdcs, err := cl.Config.GetKDCs(realm, false)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
conn, err := dialKDCUDP(count, kdcs)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r, err = cl.sendUDP(conn, b)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return checkForKRBError(r)
|
||||
}
|
||||
|
||||
// sendKDCTCP sends bytes to the KDC via TCP.
|
||||
func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
count, kdcs, err := cl.Config.GetKDCs(realm, true)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
conn, err := dialKDCTCP(count, kdcs)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
rb, err := cl.sendTCP(conn, b)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
return checkForKRBError(rb)
|
||||
}
|
||||
|
||||
// sendUDP sends bytes to connection over UDP.
|
||||
func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
|
||||
var r []byte
|
||||
defer conn.Close()
|
||||
_, err := conn.Write(b)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
udpbuf := make([]byte, 4096)
|
||||
n, _, err := conn.ReadFrom(udpbuf)
|
||||
r = udpbuf[:n]
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
if len(r) < 1 {
|
||||
return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// sendTCP sends bytes to connection over TCP.
|
||||
func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
|
||||
defer conn.Close()
|
||||
var r []byte
|
||||
// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, uint32(len(b)))
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
b = append(buf.Bytes(), b...)
|
||||
|
||||
_, err = conn.Write(b)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
|
||||
}
|
||||
|
||||
sh := make([]byte, 4, 4)
|
||||
_, err = conn.Read(sh)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("error reading response size header: %v", err)
|
||||
}
|
||||
s := binary.BigEndian.Uint32(sh)
|
||||
|
||||
rb := make([]byte, s, s)
|
||||
_, err = io.ReadFull(conn, rb)
|
||||
if err != nil {
|
||||
return r, fmt.Errorf("error reading response: %v", err)
|
||||
}
|
||||
if len(rb) < 1 {
|
||||
return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
|
||||
}
|
||||
return rb, nil
|
||||
}
|
||||
|
||||
// checkForKRBError checks if the response bytes from the KDC are a KRBError.
|
||||
func checkForKRBError(b []byte) ([]byte, error) {
|
||||
var KRBErr messages.KRBError
|
||||
if err := KRBErr.Unmarshal(b); err == nil {
|
||||
return b, KRBErr
|
||||
}
|
||||
return b, nil
|
||||
}
|
95
vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
generated
vendored
Normal file
95
vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/kadmin"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
)
|
||||
|
||||
// Kpasswd server response codes.
|
||||
const (
|
||||
KRB5_KPASSWD_SUCCESS = 0
|
||||
KRB5_KPASSWD_MALFORMED = 1
|
||||
KRB5_KPASSWD_HARDERROR = 2
|
||||
KRB5_KPASSWD_AUTHERROR = 3
|
||||
KRB5_KPASSWD_SOFTERROR = 4
|
||||
KRB5_KPASSWD_ACCESSDENIED = 5
|
||||
KRB5_KPASSWD_BAD_VERSION = 6
|
||||
KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7
|
||||
)
|
||||
|
||||
// ChangePasswd changes the password of the client to the value provided.
|
||||
func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
|
||||
ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, err := cl.sendToKPasswd(msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = r.Decrypt(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if r.ResultCode != KRB5_KPASSWD_SUCCESS {
|
||||
return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError)
|
||||
}
|
||||
cl.Credentials.WithPassword(newPasswd)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
|
||||
_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr := kps[1]
|
||||
b, err := msg.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
|
||||
return cl.sendKPasswdUDP(b, addr)
|
||||
}
|
||||
return cl.sendKPasswdTCP(b, addr)
|
||||
}
|
||||
|
||||
func (cl *Client) sendKPasswdTCP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", kadmindAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rb, err := cl.sendTCP(conn, b)
|
||||
err = r.Unmarshal(rb)
|
||||
return
|
||||
}
|
||||
|
||||
func (cl *Client) sendKPasswdUDP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", kadmindAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn, err := net.DialUDP("udp", nil, udpAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rb, err := cl.sendUDP(conn, b)
|
||||
err = r.Unmarshal(rb)
|
||||
return
|
||||
}
|
295
vendor/github.com/jcmturner/gokrb5/v8/client/session.go
generated
vendored
Normal file
295
vendor/github.com/jcmturner/gokrb5/v8/client/session.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jcmturner/gokrb5/v8/iana/nametype"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// sessions hold TGTs and are keyed on the realm name
|
||||
type sessions struct {
|
||||
Entries map[string]*session
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// destroy erases all sessions
|
||||
func (s *sessions) destroy() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
for k, e := range s.Entries {
|
||||
e.destroy()
|
||||
delete(s.Entries, k)
|
||||
}
|
||||
}
|
||||
|
||||
// update replaces a session with the one provided or adds it as a new one
|
||||
func (s *sessions) update(sess *session) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
// if a session already exists for this, cancel its auto renew.
|
||||
if i, ok := s.Entries[sess.realm]; ok {
|
||||
if i != sess {
|
||||
// Session in the sessions cache is not the same as one provided.
|
||||
// Cancel the one in the cache and add this one.
|
||||
i.mux.Lock()
|
||||
defer i.mux.Unlock()
|
||||
i.cancel <- true
|
||||
s.Entries[sess.realm] = sess
|
||||
return
|
||||
}
|
||||
}
|
||||
// No session for this realm was found so just add it
|
||||
s.Entries[sess.realm] = sess
|
||||
}
|
||||
|
||||
// get returns the session for the realm specified
|
||||
func (s *sessions) get(realm string) (*session, bool) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
sess, ok := s.Entries[realm]
|
||||
return sess, ok
|
||||
}
|
||||
|
||||
// session holds the TGT details for a realm
|
||||
type session struct {
|
||||
realm string
|
||||
authTime time.Time
|
||||
endTime time.Time
|
||||
renewTill time.Time
|
||||
tgt messages.Ticket
|
||||
sessionKey types.EncryptionKey
|
||||
sessionKeyExpiration time.Time
|
||||
cancel chan bool
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// jsonSession is used to enable marshaling some information of a session in a JSON format
|
||||
type jsonSession struct {
|
||||
Realm string
|
||||
AuthTime time.Time
|
||||
EndTime time.Time
|
||||
RenewTill time.Time
|
||||
SessionKeyExpiration time.Time
|
||||
}
|
||||
|
||||
// AddSession adds a session for a realm with a TGT to the client's session cache.
|
||||
// A goroutine is started to automatically renew the TGT before expiry.
|
||||
func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
|
||||
if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" {
|
||||
// Not a TGT
|
||||
return
|
||||
}
|
||||
realm := tgt.SName.NameString[len(tgt.SName.NameString)-1]
|
||||
s := &session{
|
||||
realm: realm,
|
||||
authTime: dep.AuthTime,
|
||||
endTime: dep.EndTime,
|
||||
renewTill: dep.RenewTill,
|
||||
tgt: tgt,
|
||||
sessionKey: dep.Key,
|
||||
sessionKeyExpiration: dep.KeyExpiration,
|
||||
}
|
||||
cl.sessions.update(s)
|
||||
cl.enableAutoSessionRenewal(s)
|
||||
cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime)
|
||||
}
|
||||
|
||||
// update overwrites the session details with those from the TGT and decrypted encPart
|
||||
func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
s.authTime = dep.AuthTime
|
||||
s.endTime = dep.EndTime
|
||||
s.renewTill = dep.RenewTill
|
||||
s.tgt = tgt
|
||||
s.sessionKey = dep.Key
|
||||
s.sessionKeyExpiration = dep.KeyExpiration
|
||||
}
|
||||
|
||||
// destroy will cancel any auto renewal of the session and set the expiration times to the current time
|
||||
func (s *session) destroy() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
if s.cancel != nil {
|
||||
s.cancel <- true
|
||||
}
|
||||
s.endTime = time.Now().UTC()
|
||||
s.renewTill = s.endTime
|
||||
s.sessionKeyExpiration = s.endTime
|
||||
}
|
||||
|
||||
// valid informs if the TGT is still within the valid time window
|
||||
func (s *session) valid() bool {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
t := time.Now().UTC()
|
||||
if t.Before(s.endTime) && s.authTime.Before(t) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tgtDetails is a thread safe way to get the session's realm, TGT and session key values
|
||||
func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
return s.realm, s.tgt, s.sessionKey
|
||||
}
|
||||
|
||||
// timeDetails is a thread safe way to get the session's validity time values
|
||||
func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration
|
||||
}
|
||||
|
||||
// JSON return information about the held sessions in a JSON format.
|
||||
func (s *sessions) JSON() (string, error) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
var js []jsonSession
|
||||
keys := make([]string, 0, len(s.Entries))
|
||||
for k := range s.Entries {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
r, at, et, rt, kt := s.Entries[k].timeDetails()
|
||||
j := jsonSession{
|
||||
Realm: r,
|
||||
AuthTime: at,
|
||||
EndTime: et,
|
||||
RenewTill: rt,
|
||||
SessionKeyExpiration: kt,
|
||||
}
|
||||
js = append(js, j)
|
||||
}
|
||||
b, err := json.MarshalIndent(js, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
|
||||
func (cl *Client) enableAutoSessionRenewal(s *session) {
|
||||
var timer *time.Timer
|
||||
s.mux.Lock()
|
||||
s.cancel = make(chan bool, 1)
|
||||
s.mux.Unlock()
|
||||
go func(s *session) {
|
||||
for {
|
||||
s.mux.RLock()
|
||||
w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6
|
||||
s.mux.RUnlock()
|
||||
if w < 0 {
|
||||
return
|
||||
}
|
||||
timer = time.NewTimer(w)
|
||||
select {
|
||||
case <-timer.C:
|
||||
renewal, err := cl.refreshSession(s)
|
||||
if err != nil {
|
||||
cl.Log("error refreshing session: %v", err)
|
||||
}
|
||||
if !renewal && err == nil {
|
||||
// end this goroutine as there will have been a new login and new auto renewal goroutine created.
|
||||
return
|
||||
}
|
||||
case <-s.cancel:
|
||||
// cancel has been called. Stop the timer and exit.
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
// renewTGT renews the client's TGT session.
|
||||
func (cl *Client) renewTGT(s *session) error {
|
||||
realm, tgt, skey := s.tgtDetails()
|
||||
spn := types.PrincipalName{
|
||||
NameType: nametype.KRB_NT_SRV_INST,
|
||||
NameString: []string{"krbtgt", realm},
|
||||
}
|
||||
_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true)
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm)
|
||||
}
|
||||
s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart)
|
||||
cl.sessions.update(s)
|
||||
cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// refreshSession updates either through renewal or creating a new login.
|
||||
// The boolean indicates if the update was a renewal.
|
||||
func (cl *Client) refreshSession(s *session) (bool, error) {
|
||||
s.mux.RLock()
|
||||
realm := s.realm
|
||||
renewTill := s.renewTill
|
||||
s.mux.RUnlock()
|
||||
cl.Log("refreshing TGT session for %s", realm)
|
||||
if time.Now().UTC().Before(renewTill) {
|
||||
err := cl.renewTGT(s)
|
||||
return true, err
|
||||
}
|
||||
err := cl.realmLogin(realm)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensureValidSession makes sure there is a valid session for the realm
|
||||
func (cl *Client) ensureValidSession(realm string) error {
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if ok {
|
||||
s.mux.RLock()
|
||||
d := s.endTime.Sub(s.authTime) / 6
|
||||
if s.endTime.Sub(time.Now().UTC()) > d {
|
||||
s.mux.RUnlock()
|
||||
return nil
|
||||
}
|
||||
s.mux.RUnlock()
|
||||
_, err := cl.refreshSession(s)
|
||||
return err
|
||||
}
|
||||
return cl.realmLogin(realm)
|
||||
}
|
||||
|
||||
// sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm
|
||||
func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) {
|
||||
err = cl.ensureValidSession(realm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if !ok {
|
||||
err = fmt.Errorf("could not find TGT session for %s", realm)
|
||||
return
|
||||
}
|
||||
_, tgt, sessionKey = s.tgtDetails()
|
||||
return
|
||||
}
|
||||
|
||||
// sessionTimes provides the timing information with regards to a session for the realm specified.
|
||||
func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) {
|
||||
s, ok := cl.sessions.get(realm)
|
||||
if !ok {
|
||||
err = fmt.Errorf("could not find TGT session for %s", realm)
|
||||
return
|
||||
}
|
||||
_, authTime, endTime, renewTime, sessionExp = s.timeDetails()
|
||||
return
|
||||
}
|
||||
|
||||
// spnRealm resolves the realm name of a service principal name
|
||||
func (cl *Client) spnRealm(spn types.PrincipalName) string {
|
||||
return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
|
||||
}
|
93
vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
generated
vendored
Normal file
93
vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Settings holds optional client settings.
|
||||
type Settings struct {
|
||||
disablePAFXFast bool
|
||||
assumePreAuthentication bool
|
||||
preAuthEType int32
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// jsonSettings is used when marshaling the Settings details to JSON format.
|
||||
type jsonSettings struct {
|
||||
DisablePAFXFast bool
|
||||
AssumePreAuthentication bool
|
||||
}
|
||||
|
||||
// NewSettings creates a new client settings struct.
|
||||
func NewSettings(settings ...func(*Settings)) *Settings {
|
||||
s := new(Settings)
|
||||
for _, set := range settings {
|
||||
set(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// DisablePAFXFAST used to configure the client to not use PA_FX_FAST.
|
||||
//
|
||||
// s := NewSettings(DisablePAFXFAST(true))
|
||||
func DisablePAFXFAST(b bool) func(*Settings) {
|
||||
return func(s *Settings) {
|
||||
s.disablePAFXFast = b
|
||||
}
|
||||
}
|
||||
|
||||
// DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST.
|
||||
func (s *Settings) DisablePAFXFAST() bool {
|
||||
return s.disablePAFXFast
|
||||
}
|
||||
|
||||
// AssumePreAuthentication used to configure the client to assume pre-authentication is required.
|
||||
//
|
||||
// s := NewSettings(AssumePreAuthentication(true))
|
||||
func AssumePreAuthentication(b bool) func(*Settings) {
|
||||
return func(s *Settings) {
|
||||
s.assumePreAuthentication = b
|
||||
}
|
||||
}
|
||||
|
||||
// AssumePreAuthentication indicates if the client should proactively assume using pre-authentication.
|
||||
func (s *Settings) AssumePreAuthentication() bool {
|
||||
return s.assumePreAuthentication
|
||||
}
|
||||
|
||||
// Logger used to configure client with a logger.
|
||||
//
|
||||
// s := NewSettings(kt, Logger(l))
|
||||
func Logger(l *log.Logger) func(*Settings) {
|
||||
return func(s *Settings) {
|
||||
s.logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Logger returns the client logger instance.
|
||||
func (s *Settings) Logger() *log.Logger {
|
||||
return s.logger
|
||||
}
|
||||
|
||||
// Log will write to the service's logger if it is configured.
|
||||
func (cl *Client) Log(format string, v ...interface{}) {
|
||||
if cl.settings.Logger() != nil {
|
||||
cl.settings.Logger().Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// JSON returns a JSON representation of the settings.
|
||||
func (s *Settings) JSON() (string, error) {
|
||||
js := jsonSettings{
|
||||
DisablePAFXFast: s.disablePAFXFast,
|
||||
AssumePreAuthentication: s.assumePreAuthentication,
|
||||
}
|
||||
b, err := json.MarshalIndent(js, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
|
||||
}
|
Reference in New Issue
Block a user