client.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Package client provides a client library and methods for Kerberos 5 authentication.
  2. package client
  3. import (
  4. "errors"
  5. "fmt"
  6. "gopkg.in/jcmturner/gokrb5.v4/config"
  7. "gopkg.in/jcmturner/gokrb5.v4/credentials"
  8. "gopkg.in/jcmturner/gokrb5.v4/crypto"
  9. "gopkg.in/jcmturner/gokrb5.v4/crypto/etype"
  10. "gopkg.in/jcmturner/gokrb5.v4/iana/errorcode"
  11. "gopkg.in/jcmturner/gokrb5.v4/iana/nametype"
  12. "gopkg.in/jcmturner/gokrb5.v4/keytab"
  13. "gopkg.in/jcmturner/gokrb5.v4/messages"
  14. "gopkg.in/jcmturner/gokrb5.v4/types"
  15. )
  16. // Client side configuration and state.
  17. type Client struct {
  18. Credentials *credentials.Credentials
  19. Config *config.Config
  20. GoKrb5Conf *Config
  21. sessions *sessions
  22. Cache *Cache
  23. }
  24. // Config struct holds GoKRB5 specific client configurations.
  25. // Set Disable_PA_FX_FAST to true to force this behaviour off.
  26. // Set Assume_PA_ENC_TIMESTAMP_Required to send the PA_ENC_TIMESTAMP pro-actively rather than waiting for a KRB_ERROR response from the KDC indicating it is required.
  27. type Config struct {
  28. DisablePAFXFast bool
  29. AssumePAEncTimestampRequired bool
  30. }
  31. // NewClientWithPassword creates a new client from a password credential.
  32. // Set the realm to empty string to use the default realm from config.
  33. func NewClientWithPassword(username, realm, password string) Client {
  34. creds := credentials.NewCredentials(username, realm)
  35. return Client{
  36. Credentials: creds.WithPassword(password),
  37. Config: config.NewConfig(),
  38. GoKrb5Conf: &Config{},
  39. sessions: &sessions{
  40. Entries: make(map[string]*session),
  41. },
  42. Cache: NewCache(),
  43. }
  44. }
  45. // NewClientWithKeytab creates a new client from a keytab credential.
  46. func NewClientWithKeytab(username, realm string, kt keytab.Keytab) Client {
  47. creds := credentials.NewCredentials(username, realm)
  48. return Client{
  49. Credentials: creds.WithKeytab(kt),
  50. Config: config.NewConfig(),
  51. GoKrb5Conf: &Config{},
  52. sessions: &sessions{
  53. Entries: make(map[string]*session),
  54. },
  55. Cache: NewCache(),
  56. }
  57. }
  58. // NewClientFromCCache create a client from a populated client cache.
  59. //
  60. // WARNING: If you do not add a keytab or password to the client then the TGT cannot be renewed and a failure will occur after the TGT expires.
  61. func NewClientFromCCache(c credentials.CCache) (Client, error) {
  62. cl := Client{
  63. Credentials: c.GetClientCredentials(),
  64. Config: config.NewConfig(),
  65. GoKrb5Conf: &Config{},
  66. sessions: &sessions{
  67. Entries: make(map[string]*session),
  68. },
  69. Cache: NewCache(),
  70. }
  71. spn := types.PrincipalName{
  72. NameType: nametype.KRB_NT_SRV_INST,
  73. NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
  74. }
  75. cred, ok := c.GetEntry(spn)
  76. if !ok {
  77. return cl, errors.New("TGT not found in CCache")
  78. }
  79. var tgt messages.Ticket
  80. err := tgt.Unmarshal(cred.Ticket)
  81. if err != nil {
  82. return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
  83. }
  84. cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
  85. Realm: c.DefaultPrincipal.Realm,
  86. AuthTime: cred.AuthTime,
  87. EndTime: cred.EndTime,
  88. RenewTill: cred.RenewTill,
  89. TGT: tgt,
  90. SessionKey: cred.Key,
  91. }
  92. for _, cred := range c.GetEntries() {
  93. var tkt messages.Ticket
  94. err = tkt.Unmarshal(cred.Ticket)
  95. if err != nil {
  96. return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
  97. }
  98. cl.Cache.addEntry(
  99. tkt,
  100. cred.AuthTime,
  101. cred.StartTime,
  102. cred.EndTime,
  103. cred.RenewTill,
  104. cred.Key,
  105. )
  106. }
  107. return cl, nil
  108. }
  109. // WithConfig sets the Kerberos configuration for the client.
  110. func (cl *Client) WithConfig(cfg *config.Config) *Client {
  111. cl.Config = cfg
  112. return cl
  113. }
  114. // WithKeytab adds a keytab to the client
  115. func (cl *Client) WithKeytab(kt keytab.Keytab) *Client {
  116. cl.Credentials.WithKeytab(kt)
  117. return cl
  118. }
  119. // WithPassword adds a password to the client
  120. func (cl *Client) WithPassword(password string) *Client {
  121. cl.Credentials.WithPassword(password)
  122. return cl
  123. }
  124. // Key returns a key for the client. Preferably from a keytab and then generated from the password.
  125. // The KRBError would have been returned from the KDC and must be of type KDC_ERR_PREAUTH_REQUIRED.
  126. // If a KRBError is not available pass nil and a key will be returned from the credentials keytab.
  127. func (cl *Client) Key(etype etype.EType, krberr messages.KRBError) (types.EncryptionKey, error) {
  128. if cl.Credentials.HasKeytab() && etype != nil {
  129. return cl.Credentials.Keytab.GetEncryptionKey(cl.Credentials.CName.NameString, cl.Credentials.Realm, 0, etype.GetETypeID())
  130. } else if cl.Credentials.HasPassword() {
  131. if krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
  132. var pas types.PADataSequence
  133. err := pas.Unmarshal(krberr.EData)
  134. if err != nil {
  135. return types.EncryptionKey{}, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
  136. }
  137. key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
  138. return key, err
  139. }
  140. key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, cl.Credentials.CName, cl.Credentials.Realm, etype.GetETypeID(), types.PADataSequence{})
  141. return key, err
  142. }
  143. return types.EncryptionKey{}, errors.New("credential has neither keytab or password to generate key")
  144. }
  145. // LoadConfig loads the Kerberos configuration for the client from file path specified.
  146. func (cl *Client) LoadConfig(cfgPath string) (*Client, error) {
  147. cfg, err := config.Load(cfgPath)
  148. if err != nil {
  149. return cl, err
  150. }
  151. cl.Config = cfg
  152. return cl, nil
  153. }
  154. // IsConfigured indicates if the client has the values required set.
  155. func (cl *Client) IsConfigured() (bool, error) {
  156. // Client needs to have either a password, keytab or a session already (later when loading from CCache)
  157. if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
  158. sess, err := cl.GetSessionFromRealm(cl.Config.LibDefaults.DefaultRealm)
  159. if err != nil || sess.AuthTime.IsZero() {
  160. return false, errors.New("client has neither a keytab nor a password set and no session")
  161. }
  162. }
  163. if cl.Credentials.Username == "" {
  164. return false, errors.New("client does not have a username")
  165. }
  166. if cl.Config.LibDefaults.DefaultRealm == "" {
  167. return false, errors.New("client krb5 config does not have a default realm")
  168. }
  169. if !cl.Config.LibDefaults.DNSLookupKDC {
  170. for _, r := range cl.Config.Realms {
  171. if r.Realm == cl.Config.LibDefaults.DefaultRealm {
  172. if len(r.KDC) > 0 {
  173. return true, nil
  174. }
  175. return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
  176. }
  177. }
  178. }
  179. return true, nil
  180. }
  181. // Login the client with the KDC via an AS exchange.
  182. func (cl *Client) Login() error {
  183. if cl.Credentials.Realm == "" {
  184. cl.Credentials.Realm = cl.Config.LibDefaults.DefaultRealm
  185. }
  186. return cl.ASExchange(cl.Credentials.Realm, 0)
  187. }