Jonathan Turner 9 lat temu
rodzic
commit
9131d9815e
7 zmienionych plików z 124 dodań i 34 usunięć
  1. 14 0
      GSSAPI/gssapi.go
  2. 5 7
      README.md
  3. 1 0
      client/TGSExchange.go
  4. 5 2
      client/cache.go
  5. 13 10
      client/http.go
  6. 39 0
      examples/httpServer.go
  7. 47 15
      service/http_test.go

+ 14 - 0
GSSAPI/gssapi.go

@@ -6,6 +6,9 @@ import (
 	"fmt"
 	"github.com/jcmturner/asn1"
 	"github.com/jcmturner/gokrb5/asn1tools"
+	"github.com/jcmturner/gokrb5/credentials"
+	"github.com/jcmturner/gokrb5/messages"
+	"github.com/jcmturner/gokrb5/types"
 )
 
 var SPNEGO_OID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
@@ -82,3 +85,14 @@ func (s *SPNEGO) Marshal() ([]byte, error) {
 	}
 	return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp")
 }
+
+func GetSPNEGOKrbNegTokenInit(creds credentials.Credentials, tkt messages.Ticket, sessionKey types.EncryptionKey) (SPNEGO, error) {
+	negTokenInit, err := NewNegTokenInitKrb5(creds, tkt, sessionKey)
+	if err != nil {
+		return SPNEGO{}, fmt.Errorf("Could not create NegTokenInit: %v", err)
+	}
+	return SPNEGO{
+		Init:         true,
+		NegTokenInit: negTokenInit,
+	}, nil
+}

+ 5 - 7
README.md

@@ -1,17 +1,15 @@
 # gokrb5
 [![GoDoc](https://godoc.org/github.com/jcmturner/gokrb5?status.svg)](https://godoc.org/github.com/jcmturner/gokrb5)
 
-#### This is work in progress and may have some issues. Full testing is still required.
-
 Currently the following is working/tested:
-* Tested against MIT KDC only. MIT KDC 1.6.3 is the oldest version tested against.
+* Client side libraries that supports authentication to HTTP servers that implement SPNEGO using Kerberos 5.
+* Service side libraries for implementing HTTP web servers using Kerberos SPNEGO authentication.
+* Tested against MIT KDC only (please let me know if you find it works against Microsoft Active Directory). MIT KDC 1.6.3 is the oldest version tested against.
 * Tested against a KDC that supports PA-FX-FAST.
 * Tested against users that have pre-authentication required using PA-ENC-TIMESTAMP.
-* Client side support for authentication to HTTP servers that implement SPNEGO using Kerberos 5.
-* Service side handling for Kerberos SPNEGO seems to be working but not yet fully tested with a browser such as firefox or chrome.
 
-## Implemented Encryption & Checksum Types
-The currently implemented encrytion types are:
+#### Implemented Encryption & Checksum Types
+The currently implemented encryption types are:
 
 | Implementation | Encryption ID | Checksum ID |
 |-------|-------------|------------|

+ 1 - 0
client/TGSExchange.go

@@ -71,6 +71,7 @@ func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.Encryptio
 	cl.Cache.AddEntry(
 		tgsRep.Ticket,
 		tgsRep.DecryptedEncPart.AuthTime,
+		tgsRep.DecryptedEncPart.StartTime,
 		tgsRep.DecryptedEncPart.EndTime,
 		tgsRep.DecryptedEncPart.RenewTill,
 		tgsRep.DecryptedEncPart.Key,

+ 5 - 2
client/cache.go

@@ -16,6 +16,7 @@ type Cache struct {
 type CacheEntry struct {
 	Ticket     messages.Ticket
 	AuthTime   time.Time
+	StartTime  time.Time
 	EndTime    time.Time
 	RenewTill  time.Time
 	SessionKey types.EncryptionKey
@@ -35,11 +36,12 @@ func (c *Cache) GetEntry(spn string) (CacheEntry, bool) {
 }
 
 // Add a ticket to the cache.
-func (c *Cache) AddEntry(tkt messages.Ticket, authTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
+func (c *Cache) AddEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
 	spn := strings.Join(tkt.SName.NameString, "/")
 	(*c).Entries[spn] = CacheEntry{
 		Ticket:     tkt,
 		AuthTime:   authTime,
+		StartTime:  startTime,
 		EndTime:    endTime,
 		RenewTill:  renewTill,
 		SessionKey: sessionKey,
@@ -57,7 +59,7 @@ func (c *Cache) RemoveEntry(spn string) {
 func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
 	if e, ok := cl.Cache.GetEntry(spn); ok {
 		//If within time window of ticket return it
-		if time.Now().UTC().After(e.AuthTime) && time.Now().UTC().Before(e.EndTime) {
+		if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
 			return e.Ticket, e.SessionKey, true
 		} else if time.Now().UTC().Before(e.RenewTill) {
 			e, err := cl.RenewTicket(e)
@@ -82,6 +84,7 @@ func (cl *Client) RenewTicket(e CacheEntry) (CacheEntry, error) {
 	e = cl.Cache.AddEntry(
 		tgsRep.Ticket,
 		tgsRep.DecryptedEncPart.AuthTime,
+		tgsRep.DecryptedEncPart.StartTime,
 		tgsRep.DecryptedEncPart.EndTime,
 		tgsRep.DecryptedEncPart.RenewTill,
 		tgsRep.DecryptedEncPart.Key,

+ 13 - 10
client/http.go

@@ -4,34 +4,37 @@ import (
 	"encoding/base64"
 	"fmt"
 	"github.com/jcmturner/gokrb5/GSSAPI"
+	"github.com/jcmturner/gokrb5/credentials"
+	"github.com/jcmturner/gokrb5/messages"
+	"github.com/jcmturner/gokrb5/types"
 	"net/http"
 	"strings"
 )
 
 // Get service ticket and set as the SPNEGO authorization header on HTTP request object.
 // To auto generate the SPN from the request object pass a null string "".
-func (cl *Client) SetSPNEGOHeader(HTTPReq *http.Request, spn string) error {
+func (cl *Client) SetSPNEGOHeader(r *http.Request, spn string) error {
 	if spn == "" {
-		spn = "HTTP/" + strings.SplitN(HTTPReq.Host, ":", 2)[0]
+		spn = "HTTP/" + strings.SplitN(r.Host, ":", 2)[0]
 	}
 	tkt, skey, err := cl.GetServiceTicket(spn)
 	if err != nil {
 		return fmt.Errorf("Could not get service ticket: %v", err)
 	}
-	negTokenInit, err := GSSAPI.NewNegTokenInitKrb5(*cl.Credentials, tkt, skey)
+	err = SetSPNEGOHeader(*cl.Credentials, tkt, skey, r)
 	if err != nil {
-		return fmt.Errorf("Could not create NegTokenInit: %v", err)
-	}
-	SPNEGOToken := GSSAPI.SPNEGO{
-		Init:         true,
-		NegTokenInit: negTokenInit,
+		return err
 	}
+	return nil
+}
+
+func SetSPNEGOHeader(creds credentials.Credentials, tkt messages.Ticket, sessionKey types.EncryptionKey, r *http.Request) error {
+	SPNEGOToken, err := GSSAPI.GetSPNEGOKrbNegTokenInit(creds, tkt, sessionKey)
 	nb, err := SPNEGOToken.Marshal()
 	if err != nil {
 		return fmt.Errorf("Could marshal SPNEGO: %v", err)
 	}
-
 	hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb)
-	HTTPReq.Header.Set("Authorization", hs)
+	r.Header.Set("Authorization", hs)
 	return nil
 }

+ 39 - 0
examples/httpServer.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"encoding/hex"
+	"fmt"
+	"github.com/jcmturner/gokrb5/keytab"
+	"github.com/jcmturner/gokrb5/service"
+	"github.com/jcmturner/gokrb5/testdata"
+	"log"
+	"net/http"
+	"os"
+)
+
+func main() {
+	// Create logger
+	l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile)
+
+	// Load the service's keytab
+	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+	kt, _ := keytab.Parse(b)
+
+	// Create the application's specific handler
+	th := http.HandlerFunc(testAppHandler)
+
+	// Set up handler mappings wrapping in the SPNEGOKRB5Authenticate handler wrapper
+	mux := http.NewServeMux()
+	mux.Handle("/", service.SPNEGOKRB5Authenticate(th, kt, l))
+
+	// Start up the web server
+	log.Fatal(http.ListenAndServe(":9080", mux))
+}
+
+// Simple application specific handler
+func testAppHandler(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(http.StatusOK)
+	ctx := r.Context()
+	fmt.Fprintf(w, "<html>\nTEST.GOKRB5 Handler\nAuthenticed user: %s\nUser's realm: %s\n</html>", ctx.Value("cname").(string), ctx.Value("crealm").(string))
+	return
+}

+ 47 - 15
service/service_integration_test.go → service/http_test.go

@@ -1,30 +1,26 @@
-// +build integration
-// To turn on this test use -tags=integration in go test command
-
 package service
 
 import (
 	"encoding/hex"
 	"fmt"
+	"github.com/jcmturner/gokrb5/client"
+	"github.com/jcmturner/gokrb5/iana/nametype"
 	"github.com/jcmturner/gokrb5/keytab"
+	"github.com/jcmturner/gokrb5/messages"
 	"github.com/jcmturner/gokrb5/testdata"
+	"github.com/jcmturner/gokrb5/types"
 	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"log"
 	"net/http"
 	"net/http/httptest"
 	"testing"
+	"time"
 )
 
 func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) {
 	s := httpServer()
 	defer s.Close()
-
-	cl := getClient()
-	err := cl.Login()
-	if err != nil {
-		t.Fatalf("Error on AS_REQ: %v\n", err)
-	}
 	r, _ := http.NewRequest("GET", s.URL, nil)
 	httpResp, err := http.DefaultClient.Do(r)
 	if err != nil {
@@ -39,12 +35,30 @@ func TestService_SPNEGOKRB_ValidUser(t *testing.T) {
 	defer s.Close()
 
 	cl := getClient()
-	err := cl.Login()
+	sname := types.PrincipalName{
+		NameType:   nametype.KRB_NT_PRINCIPAL,
+		NameString: []string{"HTTP", "host.test.gokrb5"},
+	}
+	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+	kt, _ := keytab.Parse(b)
+	st := time.Now().UTC()
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+		sname, "TEST.GOKRB5",
+		types.NewKrbFlags(),
+		kt,
+		18,
+		1,
+		st,
+		st,
+		st.Add(time.Duration(24)*time.Hour),
+		st.Add(time.Duration(48)*time.Hour),
+	)
 	if err != nil {
-		t.Fatalf("Error on AS_REQ: %v\n", err)
+		t.Fatalf("Error getting test ticket: %v", err)
 	}
+
 	r, _ := http.NewRequest("GET", s.URL, nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r)
 	if err != nil {
 		t.Fatalf("Error setting client SPNEGO header: %v", err)
 	}
@@ -60,12 +74,30 @@ func TestService_SPNEGOKRB_Replay(t *testing.T) {
 	defer s.Close()
 
 	cl := getClient()
-	err := cl.Login()
+	sname := types.PrincipalName{
+		NameType:   nametype.KRB_NT_PRINCIPAL,
+		NameString: []string{"HTTP", "host.test.gokrb5"},
+	}
+	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+	kt, _ := keytab.Parse(b)
+	st := time.Now().UTC()
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+		sname, "TEST.GOKRB5",
+		types.NewKrbFlags(),
+		kt,
+		18,
+		1,
+		st,
+		st,
+		st.Add(time.Duration(24)*time.Hour),
+		st.Add(time.Duration(48)*time.Hour),
+	)
 	if err != nil {
-		t.Fatalf("Error on AS_REQ: %v\n", err)
+		t.Fatalf("Error getting test ticket: %v", err)
 	}
+
 	r, _ := http.NewRequest("GET", s.URL, nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r)
 	if err != nil {
 		t.Fatalf("Error setting client SPNEGO header: %v", err)
 	}