Browse Source

tgs exchange and client cache

Jonathan Turner 9 years ago
parent
commit
5023917440
7 changed files with 144 additions and 32 deletions
  1. 2 0
      README.md
  2. 31 1
      client/TGSExchange.go
  3. 79 0
      client/cache.go
  4. 3 0
      client/client.go
  5. 6 3
      debug.go
  6. 22 26
      messages/KDCRep.go
  7. 1 2
      messages/KDCReq.go

+ 2 - 0
README.md

@@ -15,6 +15,8 @@ Go version 1.8+ is needed.
 [text](https://www.ietf.org/rfc/rfc3961.txt) [html](https://tools.ietf.org/html/rfc3961)
 * RFC 3962 Advanced Encryption Standard (AES) Encryption for Kerberos 5
 [text](https://www.ietf.org/rfc/rfc3962.txt) [html](https://tools.ietf.org/html/rfc3962)
+* The Simple and Protected Generic Security Service Application Program Interface (GSS-API) Negotiation Mechanism [text](https://www.ietf.org/rfc/rfc4178.txt) [html](https://tools.ietf.org/html/rfc4178.html)
+* SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows [text](https://www.ietf.org/rfc/rfc4559.txt) [html](https://tools.ietf.org/html/rfc4559.html)
 * RFC 6806 Kerberos Principal Name Canonicalization and Cross-Realm Referrals [text](https://www.ietf.org/rfc/rfc6806.txt) [html](https://tools.ietf.org/html/rfc6806.html)
 * RFC 6113 A Generalized Framework for Kerberos Pre-Authentication [text](https://www.ietf.org/rfc/rfc6113.txt) [html](https://tools.ietf.org/html/rfc6113.html)
 * [IANA Assigned Kerberos Numbers](http://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml)

+ 31 - 1
client/TGSExchange.go

@@ -1,6 +1,36 @@
 package client
 
-func (cl *Client) TGSExchange() error {
+import (
+	"errors"
+	"fmt"
+	"github.com/jcmturner/gokrb5/messages"
+)
 
+func (cl *Client) TGSExchange(spn string) error {
+	if cl.Session == nil {
+		return errors.New("Error client does not have a session. Client needs to login first")
+	}
+	tgs, err := messages.NewTGSReq(cl.Credentials.Username, cl.Config, cl.Session.TGT, cl.Session.SessionKey, "HTTP/host.test.gokrb5")
+	if err != nil {
+		return fmt.Errorf("Error generating New TGS_REQ: %v", err)
+	}
+	b, err := tgs.Marshal()
+	if err != nil {
+		return fmt.Errorf("Error marshalling TGS_REQ: %v", err)
+	}
+	r, err := cl.SendToKDC(b)
+	if err != nil {
+		return fmt.Errorf("Error sending TGS_REQ to KDC: %v", err)
+	}
+	var tgsRep messages.TGSRep
+	err = tgsRep.Unmarshal(r)
+	if err != nil {
+		return fmt.Errorf("Error unmarshalling TGS_REP: %v", err)
+	}
+	err = tgsRep.DecryptEncPart(cl.Session.SessionKey)
+	if err != nil {
+		return fmt.Errorf("Error decrypting EncPart of TGS_REP: %v", err)
+	}
+	cl.Cache.AddEntry(tgsRep.Ticket, tgsRep.DecryptedEncPart.AuthTime, tgsRep.DecryptedEncPart.EndTime, tgsRep.DecryptedEncPart.RenewTill)
 	return nil
 }

+ 79 - 0
client/cache.go

@@ -0,0 +1,79 @@
+package client
+
+import (
+	"errors"
+	"fmt"
+	"github.com/jcmturner/gokrb5/types"
+	"strings"
+	"time"
+)
+
+type Cache struct {
+	Entries map[string]CacheEntry
+}
+
+type CacheEntry struct {
+	Ticket    types.Ticket
+	AuthTime  time.Time
+	EndTime   time.Time
+	RenewTill time.Time
+	AutoRenew bool
+}
+
+func NewCache() *Cache {
+	return &Cache{
+		Entries: map[string]CacheEntry{},
+	}
+}
+
+func (c *Cache) GetEntry(spn string) (CacheEntry, bool) {
+	e, ok := (*c).Entries[spn]
+	return e, ok
+}
+
+func (c *Cache) GetTicket(spn string) (types.Ticket, bool) {
+	if e, ok := c.GetEntry(spn); ok {
+		//If within time window of ticket return it
+		if time.Now().After(e.AuthTime) && time.Now().Before(e.EndTime) {
+			return e.Ticket, true
+		}
+	}
+	var tkt types.Ticket
+	return tkt, false
+}
+
+func (c *Cache) RenewEntry(spn string) error {
+	if e, ok := c.GetEntry(spn); ok {
+		return e.Renew()
+	}
+	return fmt.Errorf("No entry for this SPN: %s", spn)
+}
+
+func (c *Cache) AddEntry(tkt types.Ticket, authTime, endTime, renewTill time.Time) {
+	(*c).Entries[strings.Join(tkt.SName.NameString, "/")] = CacheEntry{
+		Ticket:    tkt,
+		AuthTime:  authTime,
+		EndTime:   endTime,
+		RenewTill: renewTill,
+	}
+}
+
+func (c *Cache) RemoveEntry(spn string) {
+	delete(c.Entries, spn)
+}
+
+func (c *Cache) EnableAutoRenew(spn string) error {
+	return nil
+}
+
+func (c *Cache) DisableAutoRenew(spn string) error {
+	return nil
+}
+
+func (e *CacheEntry) Renew() error {
+	if time.Now().After(e.RenewTill) {
+		return errors.New("Past renew till time. Cannot renew.")
+	}
+	//TODO put renew action here
+	return nil
+}

+ 3 - 0
client/client.go

@@ -10,6 +10,7 @@ type Client struct {
 	Credentials *credentials.Credentials
 	Config      *config.Config
 	Session     *Session
+	Cache       *Cache
 }
 
 func NewClientWithPassword(username, password string) Client {
@@ -17,6 +18,7 @@ func NewClientWithPassword(username, password string) Client {
 	return Client{
 		Credentials: creds.WithPassword(password),
 		Config:      config.NewConfig(),
+		Cache:       NewCache(),
 	}
 }
 
@@ -25,6 +27,7 @@ func NewClientWithKeytab(username string, kt keytab.Keytab) Client {
 	return Client{
 		Credentials: creds.WithKeytab(kt),
 		Config:      config.NewConfig(),
+		Cache:       NewCache(),
 	}
 }
 

+ 6 - 3
debug.go

@@ -143,9 +143,12 @@ func TestTGSReq() {
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error on AS_REQ: %v\n", err)
 	}
-	fmt.Fprintf(os.Stderr, "Client: %+v\n", cl)
+	err = cl.TGSExchange("HTTP/host.test.gokrb5")
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error on TGS_REQ: %v\n", err)
+	}
 
-	tgs, err := messages.NewTGSReq("testuser1", c, cl.Session.TGT, cl.Session.SessionKey, "HTTP/host.test.gokrb5")
+	/*tgs, err := messages.NewTGSReq("testuser1", c, cl.Session.TGT, cl.Session.SessionKey, "HTTP/host.test.gokrb5")
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error on New TGS_REQ: %v\n", err)
 	}
@@ -172,6 +175,6 @@ func TestTGSReq() {
 	_, err = cl.SendToKDC(b)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "Error sending TGS_REQ to KDC: %v\n", err)
-	}
+	}*/
 
 }

+ 22 - 26
messages/KDCRep.go

@@ -14,7 +14,6 @@ import (
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/patype"
-	"github.com/jcmturner/gokrb5/keytab"
 	"github.com/jcmturner/gokrb5/types"
 	"time"
 )
@@ -201,37 +200,34 @@ func (k *ASRep) IsValid(cfg *config.Config, asReq ASReq) (bool, error) {
 		if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) {
 			return false, errors.New("KDC did not respond appropriately to FAST negotiation")
 		}
-		for _, pa := range k.DecryptedEncPart.EncPAData {
-			if pa.PADataType == patype.PA_REQ_ENC_PA_REP {
-				var pafast types.PAReqEncPARep
-				err := pafast.Unmarshal(pa.PADataValue)
-				if err != nil {
-					return false, fmt.Errorf("KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP: %v", err)
-				}
-				etype, err := crypto.GetChksumEtype(pafast.ChksumType)
-				if err != nil {
-					return false, fmt.Errorf("KDC FAST negotiation response error, %v", err)
-				}
-				ab, _ := asReq.Marshal()
-				if !crypto.VerifyChecksum(k.DecryptedEncPart.Key.KeyValue, pafast.Chksum, ab, keyusage.KEY_USAGE_AS_REQ, etype) {
-					return false, errors.New("KDC FAST negotiation response checksum invalid")
-				}
-			}
-		}
+		//TODO figure out how to check hash and put back
+		//for _, pa := range k.DecryptedEncPart.EncPAData {
+		//	if pa.PADataType == patype.PA_REQ_ENC_PA_REP {
+		//		var pafast types.PAReqEncPARep
+		//		err := pafast.Unmarshal(pa.PADataValue)
+		//		if err != nil {
+		//			return false, fmt.Errorf("KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP: %v", err)
+		//		}
+		//		etype, err := crypto.GetChksumEtype(pafast.ChksumType)
+		//		if err != nil {
+		//			return false, fmt.Errorf("KDC FAST negotiation response error, %v", err)
+		//		}
+		//		ab, _ := asReq.Marshal()
+		//		if !crypto.VerifyChecksum(k.DecryptedEncPart.Key.KeyValue, pafast.Chksum, ab, keyusage.KEY_USAGE_AS_REQ, etype) {
+		//			return false, errors.New("KDC FAST negotiation response checksum invalid")
+		//		}
+		//	}
+		//}
 	}
 	return true, nil
 }
 
-func (k *TGSRep) DecryptEncPart(kt keytab.Keytab) error {
-	etype, err := crypto.GetEtype(k.EncPart.EType)
-	if err != nil {
-		return fmt.Errorf("Keytab error: %v", err)
-	}
-	key, err := kt.GetEncryptionKey(k.CName.NameString[0], k.CRealm, k.EncPart.KVNO, k.EncPart.EType)
+func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error {
+	etype, err := crypto.GetEtype(key.KeyType)
 	if err != nil {
-		return fmt.Errorf("Could not get key from keytab: %v", err)
+		return fmt.Errorf("Could not get etype: %v", err)
 	}
-	b, err := crypto.DecryptEncPart(key.KeyValue, k.EncPart, etype, keyusage.AS_REP_ENCPART)
+	b, err := crypto.DecryptEncPart(key.KeyValue, k.EncPart, etype, keyusage.TGS_REP_ENCPART_SESSION_KEY)
 	if err != nil {
 		return fmt.Errorf("Error decrypting KDC_REP EncPart: %v", err)
 	}

+ 1 - 2
messages/KDCReq.go

@@ -166,13 +166,12 @@ func NewTGSReq(username string, c *config.Config, TGT types.Ticket, sessionKey t
 	if err != nil {
 		return a, fmt.Errorf("Error marshalling AP_REQ for pre-authentication data: %v", err)
 	}
-	pas := types.PADataSequence{
+	a.PAData = types.PADataSequence{
 		types.PAData{
 			PADataType:  patype.PA_TGS_REQ,
 			PADataValue: apb,
 		},
 	}
-	a.PAData = pas
 	return a, nil
 }