// Package client provides a client library and methods for Kerberos 5 authentication.
package client import ( ) // 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 (, , string, *config.Config, ...func(*Settings)) *Client { := credentials.New(, ) return &Client{ Credentials: .WithPassword(), Config: , settings: NewSettings(...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } } // NewWithKeytab creates a new client from a keytab credential. func (, string, *keytab.Keytab, *config.Config, ...func(*Settings)) *Client { := credentials.New(, ) return &Client{ Credentials: .WithKeytab(), Config: , settings: NewSettings(...), 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 ( *credentials.CCache, *config.Config, ...func(*Settings)) (*Client, error) { := &Client{ Credentials: .GetClientCredentials(), Config: , settings: NewSettings(...), sessions: &sessions{ Entries: make(map[string]*session), }, cache: NewCache(), } := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", .DefaultPrincipal.Realm}, } , := .GetEntry() if ! { return , errors.New("TGT not found in CCache") } var messages.Ticket := .Unmarshal(.Ticket) if != nil { return , fmt.Errorf("TGT bytes in cache are not valid: %v", ) } .sessions.Entries[.DefaultPrincipal.Realm] = &session{ realm: .DefaultPrincipal.Realm, authTime: .AuthTime, endTime: .EndTime, renewTill: .RenewTill, tgt: , sessionKey: .Key, } for , := range .GetEntries() { var messages.Ticket = .Unmarshal(.Ticket) if != nil { return , fmt.Errorf("cache entry ticket bytes are not valid: %v", ) } .cache.addEntry( , .AuthTime, .StartTime, .EndTime, .RenewTill, .Key, ) } return , 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 ( *Client) ( etype.EType, int, *messages.KRBError) (types.EncryptionKey, int, error) { if .Credentials.HasKeytab() && != nil { return .Credentials.Keytab().GetEncryptionKey(.Credentials.CName(), .Credentials.Domain(), , .GetETypeID()) } else if .Credentials.HasPassword() { if != nil && .ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED { var types.PADataSequence := .Unmarshal(.EData) if != nil { return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", ) } , , := crypto.GetKeyFromPassword(.Credentials.Password(), .CName, .CRealm, .GetETypeID(), ) return , 0, } , , := crypto.GetKeyFromPassword(.Credentials.Password(), .Credentials.CName(), .Credentials.Domain(), .GetETypeID(), types.PADataSequence{}) return , 0, } 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 ( *Client) () (bool, error) { if .Credentials.UserName() == "" { return false, errors.New("client does not have a username") } if .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 !.Credentials.HasPassword() && !.Credentials.HasKeytab() { , , , , := .sessionTimes(.Credentials.Domain()) if != nil || .IsZero() { return false, errors.New("client has neither a keytab nor a password set and no session") } } if !.Config.LibDefaults.DNSLookupKDC { for , := range .Config.Realms { if .Realm == .Credentials.Domain() { if len(.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 ( *Client) () error { if , := .IsConfigured(); ! { return } if !.Credentials.HasPassword() && !.Credentials.HasKeytab() { , , , , := .sessionTimes(.Credentials.Domain()) if != nil { return krberror.Errorf(, krberror.KRBMsgError, "no user credentials available and error getting any existing session") } if time.Now().UTC().After() { 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 } , := messages.NewASReqForTGT(.Credentials.Domain(), .Config, .Credentials.CName()) if != nil { return krberror.Errorf(, krberror.KRBMsgError, "error generating new AS_REQ") } , := .ASExchange(.Credentials.Domain(), , 0) if != nil { return } .addSession(.Ticket, .DecryptedEncPart) return nil } // AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT. func ( *Client) () error { , , , , := .sessionTimes(.Credentials.Domain()) if != nil || time.Now().UTC().After() { := .Login() if != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", ) } } return nil } // realmLogin obtains or renews a TGT and establishes a session for the realm specified. func ( *Client) ( string) error { if == .Credentials.Domain() { return .Login() } , , , , := .sessionTimes(.Credentials.Domain()) if != nil || time.Now().UTC().After() { := .Login() if != nil { return fmt.Errorf("could not get valid TGT for client's realm: %v", ) } } , , := .sessionTGT(.Credentials.Domain()) if != nil { return } := types.PrincipalName{ NameType: nametype.KRB_NT_SRV_INST, NameString: []string{"krbtgt", }, } , , := .TGSREQGenerateAndExchange(, .Credentials.Domain(), , , false) if != nil { return } .addSession(.Ticket, .DecryptedEncPart) return nil } // Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client. func ( *Client) () { := credentials.New("", "") .sessions.destroy() .cache.clear() .Credentials = .Log("client destroyed") } // Diagnostics runs a set of checks that the client is properly configured and writes details to the io.Writer provided. func ( *Client) ( io.Writer) error { .Print() var []string if .Credentials.HasKeytab() { var []int32 for , := range .Credentials.Keytab().Entries { if .Principal.Realm == .Credentials.Realm() { = append(, .Key.KeyType) } } for , := range .Config.LibDefaults.DefaultTktEnctypeIDs { var bool for , := range { if == { = true break } } if ! { = append(, fmt.Sprintf("default_tkt_enctypes specifies %d but this enctype is not available in the client's keytab", )) } } for , := range .Config.LibDefaults.PreferredPreauthTypes { var bool for , := range { if int() == { = true break } } if ! { = append(, fmt.Sprintf("preferred_preauth_types specifies %d but this enctype is not available in the client's keytab", )) } } } , , := .Config.GetKDCs(.Credentials.Realm(), false) if != nil { = append(, fmt.Sprintf("error when resolving KDCs for UDP communication: %v", )) } if < 1 { = append(, "no KDCs resolved for communication via UDP.") } else { , := json.MarshalIndent(&, "", " ") fmt.Fprintf(, "UDP KDCs: %s\n", string()) } , , := .Config.GetKDCs(.Credentials.Realm(), false) if != nil { = append(, fmt.Sprintf("error when resolving KDCs for TCP communication: %v", )) } if < 1 { = append(, "no KDCs resolved for communication via TCP.") } else { , := json.MarshalIndent(&, "", " ") fmt.Fprintf(, "TCP KDCs: %s\n", string()) } if == nil || len() < 1 { return nil } = fmt.Errorf(strings.Join(, "\n")) return } // Print writes the details of the client to the io.Writer provided. func ( *Client) ( io.Writer) { , := .Credentials.JSON() fmt.Fprintf(, "Credentials:\n%s\n", ) , := .sessions.JSON() fmt.Fprintf(, "TGT Sessions:\n%s\n", ) , _ = .cache.JSON() fmt.Fprintf(, "Service ticket cache:\n%s\n", ) , _ = .settings.JSON() fmt.Fprintf(, "Settings:\n%s\n", ) , := .Config.JSON() fmt.Fprintf(, "Krb5 config:\n%s\n", ) , := .Credentials.Keytab().JSON() fmt.Fprintf(, "Keytab:\n%s\n", ) }