client.go 8.2 KB

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