client.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Package client provides a client library and methods for Kerberos 5 authentication.
  2. package client
  3. import (
  4. "errors"
  5. "fmt"
  6. "time"
  7. "gopkg.in/jcmturner/gokrb5.v6/config"
  8. "gopkg.in/jcmturner/gokrb5.v6/credentials"
  9. "gopkg.in/jcmturner/gokrb5.v6/crypto"
  10. "gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
  11. "gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
  12. "gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
  13. "gopkg.in/jcmturner/gokrb5.v6/keytab"
  14. "gopkg.in/jcmturner/gokrb5.v6/krberror"
  15. "gopkg.in/jcmturner/gokrb5.v6/messages"
  16. "gopkg.in/jcmturner/gokrb5.v6/types"
  17. )
  18. // Client side configuration and state.
  19. type Client struct {
  20. Credentials *credentials.Credentials
  21. Config *config.Config
  22. GoKrb5Conf Config
  23. sessions *sessions
  24. cache *Cache
  25. }
  26. // Config struct holds GoKRB5 specific client configurations.
  27. // Set Disable_PA_FX_FAST to true to force this behaviour off.
  28. // 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.
  29. type Config struct {
  30. DisablePAFXFast bool
  31. AssumePAEncTimestampRequired bool
  32. preAuthEType int32
  33. }
  34. // NewClientWithPassword creates a new client from a password credential.
  35. // Set the realm to empty string to use the default realm from config.
  36. func NewClientWithPassword(username, realm, password string) Client {
  37. creds := credentials.NewCredentials(username, realm)
  38. return Client{
  39. Credentials: creds.WithPassword(password),
  40. Config: config.NewConfig(),
  41. GoKrb5Conf: Config{},
  42. sessions: &sessions{
  43. Entries: make(map[string]*session),
  44. },
  45. cache: NewCache(),
  46. }
  47. }
  48. // NewClientWithKeytab creates a new client from a keytab credential.
  49. func NewClientWithKeytab(username, realm string, kt keytab.Keytab) Client {
  50. creds := credentials.NewCredentials(username, realm)
  51. return Client{
  52. Credentials: creds.WithKeytab(kt),
  53. Config: config.NewConfig(),
  54. GoKrb5Conf: Config{},
  55. sessions: &sessions{
  56. Entries: make(map[string]*session),
  57. },
  58. cache: NewCache(),
  59. }
  60. }
  61. // NewClientFromCCache create a client from a populated client cache.
  62. //
  63. // WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
  64. func NewClientFromCCache(c credentials.CCache) (Client, error) {
  65. cl := Client{
  66. Credentials: c.GetClientCredentials(),
  67. Config: config.NewConfig(),
  68. GoKrb5Conf: Config{},
  69. sessions: &sessions{
  70. Entries: make(map[string]*session),
  71. },
  72. cache: NewCache(),
  73. }
  74. spn := types.PrincipalName{
  75. NameType: nametype.KRB_NT_SRV_INST,
  76. NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
  77. }
  78. cred, ok := c.GetEntry(spn)
  79. if !ok {
  80. return cl, errors.New("TGT not found in CCache")
  81. }
  82. var tgt messages.Ticket
  83. err := tgt.Unmarshal(cred.Ticket)
  84. if err != nil {
  85. return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
  86. }
  87. cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
  88. realm: c.DefaultPrincipal.Realm,
  89. authTime: cred.AuthTime,
  90. endTime: cred.EndTime,
  91. renewTill: cred.RenewTill,
  92. tgt: tgt,
  93. sessionKey: cred.Key,
  94. }
  95. for _, cred := range c.GetEntries() {
  96. var tkt messages.Ticket
  97. err = tkt.Unmarshal(cred.Ticket)
  98. if err != nil {
  99. return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
  100. }
  101. cl.cache.addEntry(
  102. tkt,
  103. cred.AuthTime,
  104. cred.StartTime,
  105. cred.EndTime,
  106. cred.RenewTill,
  107. cred.Key,
  108. )
  109. }
  110. return cl, nil
  111. }
  112. // WithConfig sets the Kerberos configuration for the client.
  113. func (cl *Client) WithConfig(cfg *config.Config) *Client {
  114. cl.Config = cfg
  115. return cl
  116. }
  117. // WithKeytab adds a keytab to the client
  118. func (cl *Client) WithKeytab(kt keytab.Keytab) *Client {
  119. cl.Credentials.WithKeytab(kt)
  120. return cl
  121. }
  122. // WithPassword adds a password to the client
  123. func (cl *Client) WithPassword(password string) *Client {
  124. cl.Credentials.WithPassword(password)
  125. return cl
  126. }
  127. // Key returns a key for the client. Preferably from a keytab and then generated from the password.
  128. // The KRBError would have been returned from the KDC and must be of type KDC_ERR_PREAUTH_REQUIRED.
  129. // If a KRBError is not available pass messages.KRBError{} and a key will be returned from the credentials keytab.
  130. func (cl *Client) Key(etype etype.EType, krberr messages.KRBError) (types.EncryptionKey, error) {
  131. if cl.Credentials.HasKeytab() && etype != nil {
  132. return cl.Credentials.Keytab.GetEncryptionKey(cl.Credentials.CName.NameString, cl.Credentials.Realm, 0, etype.GetETypeID())
  133. } else if cl.Credentials.HasPassword() {
  134. if krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
  135. var pas types.PADataSequence
  136. err := pas.Unmarshal(krberr.EData)
  137. if err != nil {
  138. return types.EncryptionKey{}, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
  139. }
  140. key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
  141. return key, err
  142. }
  143. key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, cl.Credentials.CName, cl.Credentials.Realm, etype.GetETypeID(), types.PADataSequence{})
  144. return key, err
  145. }
  146. return types.EncryptionKey{}, errors.New("credential has neither keytab or password to generate key")
  147. }
  148. // LoadConfig loads the Kerberos configuration for the client from file path specified.
  149. func (cl *Client) LoadConfig(cfgPath string) (*Client, error) {
  150. cfg, err := config.Load(cfgPath)
  151. if err != nil {
  152. return cl, err
  153. }
  154. cl.Config = cfg
  155. return cl, nil
  156. }
  157. // IsConfigured indicates if the client has the values required set.
  158. func (cl *Client) IsConfigured() (bool, error) {
  159. if cl.Credentials.Username == "" {
  160. return false, errors.New("client does not have a username")
  161. }
  162. if cl.Credentials.Realm == "" {
  163. return false, errors.New("client does not have a define realm")
  164. }
  165. // Client needs to have either a password, keytab or a session already (later when loading from CCache)
  166. if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
  167. authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Realm)
  168. if err != nil || authTime.IsZero() {
  169. return false, errors.New("client has neither a keytab nor a password set and no session")
  170. }
  171. }
  172. if !cl.Config.LibDefaults.DNSLookupKDC {
  173. for _, r := range cl.Config.Realms {
  174. if r.Realm == cl.Credentials.Realm {
  175. if len(r.KDC) > 0 {
  176. return true, nil
  177. }
  178. return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
  179. }
  180. }
  181. }
  182. return true, nil
  183. }
  184. // Login the client with the KDC via an AS exchange.
  185. func (cl *Client) Login() error {
  186. if ok, err := cl.IsConfigured(); !ok {
  187. return err
  188. }
  189. ASReq, err := messages.NewASReqForTGT(cl.Credentials.Realm, cl.Config, cl.Credentials.CName)
  190. if err != nil {
  191. return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
  192. }
  193. err = setPAData(cl, messages.KRBError{}, &ASReq)
  194. if err != nil {
  195. return krberror.Errorf(err, krberror.KRBMsgError, "failed setting AS_REQ PAData")
  196. }
  197. ASRep, err := cl.ASExchange(cl.Credentials.Realm, ASReq, 0)
  198. if err != nil {
  199. return err
  200. }
  201. cl.AddSession(ASRep.Ticket, ASRep.DecryptedEncPart)
  202. return nil
  203. }
  204. // remoteRealmSession returns the session for a realm that the client is not a member of but for which there is a trust
  205. func (cl *Client) realmLogin(realm string) error {
  206. if realm == cl.Credentials.Realm {
  207. return cl.Login()
  208. }
  209. _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Realm)
  210. if err != nil || time.Now().UTC().After(endTime) {
  211. err := cl.Login()
  212. if err != nil {
  213. return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
  214. }
  215. }
  216. tgt, skey, err := cl.sessionTGT(cl.Credentials.Realm)
  217. if err != nil {
  218. return err
  219. }
  220. spn := types.PrincipalName{
  221. NameType: nametype.KRB_NT_SRV_INST,
  222. NameString: []string{"krbtgt", realm},
  223. }
  224. _, tgsRep, err := cl.TGSExchange(spn, cl.Credentials.Realm, tgt, skey, false, 0)
  225. if err != nil {
  226. return err
  227. }
  228. cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
  229. return nil
  230. }
  231. // Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
  232. func (cl *Client) Destroy() {
  233. creds := credentials.NewCredentials("", "")
  234. cl.sessions.destroy()
  235. cl.cache.clear()
  236. cl.Credentials = &creds
  237. }