Jonathan Turner 9 лет назад
Родитель
Сommit
9ddac050ec

+ 2 - 10
GSSAPI/krb5Token.go

@@ -11,7 +11,6 @@ import (
 	"github.com/jcmturner/gokrb5/iana/chksumtype"
 	"github.com/jcmturner/gokrb5/iana/chksumtype"
 	"github.com/jcmturner/gokrb5/messages"
 	"github.com/jcmturner/gokrb5/messages"
 	"github.com/jcmturner/gokrb5/types"
 	"github.com/jcmturner/gokrb5/types"
-	"math/rand"
 )
 )
 
 
 const (
 const (
@@ -114,19 +113,12 @@ func NewKRB5APREQMechToken(creds credentials.Credentials, tkt messages.Ticket, s
 func newAuthenticator(creds credentials.Credentials, keyType int) types.Authenticator {
 func newAuthenticator(creds credentials.Credentials, keyType int) types.Authenticator {
 	//RFC 4121 Section 4.1.1
 	//RFC 4121 Section 4.1.1
 	auth := types.NewAuthenticator(creds.Realm, creds.CName)
 	auth := types.NewAuthenticator(creds.Realm, creds.CName)
+	etype, _ := crypto.GetEtype(keyType)
+	auth.GenerateSeqNumberAndSubKey(keyType, etype.GetKeyByteSize())
 	auth.Cksum = types.Checksum{
 	auth.Cksum = types.Checksum{
 		CksumType: chksumtype.GSSAPI,
 		CksumType: chksumtype.GSSAPI,
 		Checksum:  newAuthenticatorChksum([]int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG}),
 		Checksum:  newAuthenticatorChksum([]int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG}),
 	}
 	}
-	auth.SeqNumber = int(rand.Int31())
-	//Generate subkey value
-	etype, _ := crypto.GetEtype(keyType)
-	sk := make([]byte, etype.GetKeyByteSize())
-	rand.Read(sk)
-	auth.SubKey = types.EncryptionKey{
-		KeyType:  keyType,
-		KeyValue: sk,
-	}
 	return auth
 	return auth
 }
 }
 
 

+ 36 - 5
README.md

@@ -1,4 +1,5 @@
 # gokrb5
 # 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.
 #### This is work in progress and may have some issues. Full testing is still required.
 
 
@@ -9,8 +10,6 @@ Currently the following is working/tested:
 * Client side support for authentication to HTTP servers that implement SPNEGO using Kerberos 5.
 * 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.
 * Service side handling for Kerberos SPNEGO seems to be working but not yet fully tested with a browser such as firefox or chrome.
 
 
-[![GoDoc](https://godoc.org/github.com/jcmturner/gokrb5?status.svg)](https://godoc.org/github.com/jcmturner/gokrb5)
-
 ## Implemented Encryption & Checksum Types
 ## Implemented Encryption & Checksum Types
 The currently implemented encrytion types are:
 The currently implemented encrytion types are:
 
 
@@ -59,22 +58,54 @@ err := cl.Login
 cl.EnableAutoSessionRenewal()
 cl.EnableAutoSessionRenewal()
 ```
 ```
 #### Authenticate to a Service
 #### Authenticate to a Service
-##### Native Kerberos
+##### Generic Kerberos
 Request a Serivce ticket for a Service Principal Name (SPN).
 Request a Serivce ticket for a Service Principal Name (SPN).
 This method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with the KDC or requesting a new ticket from the KDC.
 This method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with the KDC or requesting a new ticket from the KDC.
 Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC.
 Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC.
 ```go
 ```go
 tkt, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
 tkt, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
 ```
 ```
+The steps after this will be specifc to the application protocol but it will likely involve a client/server Authentication Protocol exchange (AP exchange).
+This will involve these steps:
+* Getting the service ticket and session key for the service the client is authenticating to:
+```go
+tkt, key, err := cl.GetServiceTicket(spnStr)
+```
+* Generate a new Authenticator and generate a sequence number and subkey:
+```go
+auth := types.NewAuthenticator(cl.Credentials.Realm, cl.Credentials.CName)
+etype, _ := crypto.GetEtype(key.KeyType)
+auth.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize())
+```
+* Set the checksum on the authenticator
+The checksum is an application specific value. Set as follows:
+```go
+auth.Cksum = types.Checksum{
+		CksumType: checksumIDint,
+		Checksum:  checksumBytesSlice,
+	}
+```
+
 ##### HTTP SPNEGO
 ##### HTTP SPNEGO
 Create the HTTP request object and then call the client's SetSPNEGOHeader method passing the Service Principal Name (SPN)
 Create the HTTP request object and then call the client's SetSPNEGOHeader method passing the Service Principal Name (SPN)
 ```go
 ```go
-r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil)
+r, _ := http.
+}NewRequest("GET", "http://host.test.gokrb5/index.html", nil)
 cl.SetSPNEGOHeader(r, "")
 cl.SetSPNEGOHeader(r, "")
 HTTPResp, err := http.DefaultClient.Do(r)
 HTTPResp, err := http.DefaultClient.Do(r)
 ```
 ```
 
 
-### Kerberos Web Service
+### Kerberised Service
+#### Validating Client Details
+To validate the AP_REQ sent by the client on the service side call this method:
+```go
+if ok, creds, err := ValidateAPREQ(mt.APReq, kt, r.RemoteAddr); ok {
+        // Perform application specifc actions
+        // creds object has details about the client identity
+}
+```
+
+#### Kerberos Web Service
 A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services.
 A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services.
 To configure the wrapper the keytab for the SPN and a Logger are required:
 To configure the wrapper the keytab for the SPN and a Logger are required:
 ```go
 ```go

+ 15 - 5
credentials/credentials.go

@@ -11,11 +11,12 @@ import (
 // Contains either a keytab, password or both.
 // Contains either a keytab, password or both.
 // Keytabs are used over passwords if both are defined.
 // Keytabs are used over passwords if both are defined.
 type Credentials struct {
 type Credentials struct {
-	Username string
-	Realm    string
-	CName    types.PrincipalName
-	Keytab   keytab.Keytab
-	Password string
+	Username   string
+	Realm      string
+	CName      types.PrincipalName
+	Keytab     keytab.Keytab
+	Password   string
+	Attributes []string
 }
 }
 
 
 // Create a new Credentials struct.
 // Create a new Credentials struct.
@@ -31,6 +32,15 @@ func NewCredentials(username string, realm string) Credentials {
 	}
 	}
 }
 }
 
 
+func NewCredentialsFromPrincipal(cname types.PrincipalName, realm string) Credentials {
+	return Credentials{
+		Username: cname.GetPrincipalNameString(),
+		Realm:    realm,
+		CName:    cname,
+		Keytab:   keytab.NewKeytab(),
+	}
+}
+
 // Set the Keytab in the Credentials struct.
 // Set the Keytab in the Credentials struct.
 func (c *Credentials) WithKeytab(kt keytab.Keytab) *Credentials {
 func (c *Credentials) WithKeytab(kt keytab.Keytab) *Credentials {
 	c.Keytab = kt
 	c.Keytab = kt

+ 28 - 0
iana/flags/constants.go

@@ -0,0 +1,28 @@
+package flags
+
+const (
+	Reserved               = 0
+	Forwardable            = 1
+	Forwarded              = 2
+	Proxiable              = 3
+	Proxy                  = 4
+	AllowPostDate          = 5
+	MayPostDate            = 5
+	PostDated              = 6
+	Invalid                = 7
+	Renewable              = 8
+	Initial                = 9
+	PreAuthent             = 10
+	HWAuthent              = 11
+	OptHardwareAuth        = 11
+	RequestAnonymous       = 12
+	TransitedPolicyChecked = 12
+	OKAsDelegate           = 13
+	EncPARep               = 15
+	Canonicalize           = 15
+	DisableTransitedCheck  = 26
+	RenewableOK            = 27
+	EncTktInSkey           = 28
+	Renew                  = 30
+	Validate               = 31
+)

+ 2 - 1
messages/KDCRep.go

@@ -12,6 +12,7 @@ import (
 	"github.com/jcmturner/gokrb5/crypto"
 	"github.com/jcmturner/gokrb5/crypto"
 	"github.com/jcmturner/gokrb5/crypto/engine"
 	"github.com/jcmturner/gokrb5/crypto/engine"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/iana/flags"
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/patype"
 	"github.com/jcmturner/gokrb5/iana/patype"
@@ -220,7 +221,7 @@ func (k *ASRep) IsValid(cfg *config.Config, creds *credentials.Credentials, asRe
 		return false, fmt.Errorf("Clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds())
 		return false, fmt.Errorf("Clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds())
 	}
 	}
 	// RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11
 	// RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11
-	if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, types.EncPARep) {
+	if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, flags.EncPARep) {
 		if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) {
 		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")
 			return false, errors.New("KDC did not respond appropriately to FAST negotiation")
 		}
 		}

+ 11 - 10
messages/KDCReq.go

@@ -12,6 +12,7 @@ import (
 	"github.com/jcmturner/gokrb5/crypto/engine"
 	"github.com/jcmturner/gokrb5/crypto/engine"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/iana/flags"
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/nametype"
 	"github.com/jcmturner/gokrb5/iana/nametype"
@@ -105,16 +106,16 @@ func NewASReq(c *config.Config, cname types.PrincipalName) ASReq {
 		},
 		},
 	}
 	}
 	if c.LibDefaults.Forwardable {
 	if c.LibDefaults.Forwardable {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Forwardable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable)
 	}
 	}
 	if c.LibDefaults.Canonicalize {
 	if c.LibDefaults.Canonicalize {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Canonicalize)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize)
 	}
 	}
 	if c.LibDefaults.Proxiable {
 	if c.LibDefaults.Proxiable {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Proxiable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable)
 	}
 	}
 	if c.LibDefaults.Renew_lifetime != 0 {
 	if c.LibDefaults.Renew_lifetime != 0 {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Renewable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
 		a.ReqBody.RTime = t.Add(c.LibDefaults.Renew_lifetime)
 		a.ReqBody.RTime = t.Add(c.LibDefaults.Renew_lifetime)
 		a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour)
 		a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour)
 
 
@@ -143,21 +144,21 @@ func NewTGSReq(cname types.PrincipalName, c *config.Config, tkt Ticket, sessionK
 		},
 		},
 	}
 	}
 	if c.LibDefaults.Forwardable {
 	if c.LibDefaults.Forwardable {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Forwardable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable)
 	}
 	}
 	if c.LibDefaults.Canonicalize {
 	if c.LibDefaults.Canonicalize {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Canonicalize)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize)
 	}
 	}
 	if c.LibDefaults.Proxiable {
 	if c.LibDefaults.Proxiable {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Proxiable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable)
 	}
 	}
 	if c.LibDefaults.Renew_lifetime > time.Duration(0) {
 	if c.LibDefaults.Renew_lifetime > time.Duration(0) {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Renewable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
 		a.ReqBody.RTime = t.Add(c.LibDefaults.Renew_lifetime)
 		a.ReqBody.RTime = t.Add(c.LibDefaults.Renew_lifetime)
 	}
 	}
 	if renewal {
 	if renewal {
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Renew)
-		types.SetFlag(&a.ReqBody.KDCOptions, types.Renewable)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renew)
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
 	}
 	}
 	auth := types.NewAuthenticator(c.LibDefaults.Default_realm, cname)
 	auth := types.NewAuthenticator(c.LibDefaults.Default_realm, cname)
 	// Add the CName to make validation of the reply easier
 	// Add the CName to make validation of the reply easier

+ 85 - 0
service/APExchange.go

@@ -0,0 +1,85 @@
+package service
+
+import (
+	"fmt"
+	"github.com/jcmturner/gokrb5/credentials"
+	"github.com/jcmturner/gokrb5/crypto"
+	"github.com/jcmturner/gokrb5/iana/errorcode"
+	"github.com/jcmturner/gokrb5/iana/flags"
+	"github.com/jcmturner/gokrb5/iana/keyusage"
+	"github.com/jcmturner/gokrb5/keytab"
+	"github.com/jcmturner/gokrb5/messages"
+	"github.com/jcmturner/gokrb5/types"
+	"time"
+)
+
+// Validates an AP_REQ sent to the service. Returns a boolean for if the AP_REQ is valid and the client's principal name and realm.
+func ValidateAPREQ(APReq messages.APReq, kt keytab.Keytab, cAddr string) (bool, credentials.Credentials, error) {
+	var creds credentials.Credentials
+	err := APReq.Ticket.DecryptEncPart(kt)
+	if err != nil {
+		return false, creds, fmt.Errorf("Error decrypting encpart of service ticket provided: %v", err)
+	}
+	ab, err := crypto.DecryptEncPart(APReq.Authenticator, APReq.Ticket.DecryptedEncPart.Key, keyusage.AP_REQ_AUTHENTICATOR)
+	if err != nil {
+		return false, creds, fmt.Errorf("Error decrypting authenticator: %v", err)
+	}
+	var a types.Authenticator
+	err = a.Unmarshal(ab)
+	if err != nil {
+		return false, creds, fmt.Errorf("Error unmarshaling authenticator: %v", err)
+	}
+
+	// Check CName in Authenticator is the same as that in the ticket
+	if !a.CName.Equal(APReq.Ticket.DecryptedEncPart.CName) {
+		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket")
+		return false, creds, err
+	}
+	if len(APReq.Ticket.DecryptedEncPart.CAddr) > 0 {
+		//The addresses in the ticket (if any) are then
+		//searched for an address matching the operating-system reported
+		//address of the client.  If no match is found or the server insists on
+		//ticket addresses but none are present in the ticket, the
+		//KRB_AP_ERR_BADADDR error is returned.
+		h, err := types.GetHostAddress(cAddr)
+		if err != nil {
+			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, err.Error())
+			return false, creds, err
+		}
+		if !types.HostAddressesContains(APReq.Ticket.DecryptedEncPart.CAddr, h) {
+			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "Client address not within the list contained in the service ticket")
+			return false, creds, err
+		}
+	}
+
+	// Check the clock skew between the client and the service server
+	ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
+	t := time.Now().UTC()
+	// Hardcode 5 min max skew. May want to make this configurable
+	d := time.Duration(5) * time.Minute
+	if t.Sub(ct) > d || ct.Sub(t) > d {
+		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("Clock skew with client too large. Greater than %v seconds", d))
+		return false, creds, err
+	}
+
+	// Check for replay
+	rc := GetReplayCache(d)
+	if rc.IsReplay(d, APReq.Ticket.SName, a) {
+		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "Replay detected")
+		return false, creds, err
+	}
+
+	// Check for future tickets or invalid tickets
+	if APReq.Ticket.DecryptedEncPart.StartTime.Sub(t) > d || types.IsFlagSet(&APReq.Ticket.DecryptedEncPart.Flags, flags.Invalid) {
+		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "Service ticket provided is not yet valid")
+		return false, creds, err
+	}
+
+	// Check for expired ticket
+	if t.Sub(APReq.Ticket.DecryptedEncPart.EndTime) > d {
+		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "Service ticket provided has expired")
+		return false, creds, err
+	}
+	creds = credentials.NewCredentialsFromPrincipal(a.CName, a.CRealm)
+	return true, creds, nil
+}

+ 7 - 7
service/cache.go

@@ -30,17 +30,17 @@ them following an event that caused the server to lose track of
 recently seen authenticators.*/
 recently seen authenticators.*/
 
 
 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets.
 // Cache for tickets received from clients keyed by fully qualified client name. Used to track replay of tickets.
-type ServiceCache map[string]ClientEntries
+type ServiceCache map[string]clientEntries
 
 
 // Entries for client details sent to the service.
 // Entries for client details sent to the service.
-type ClientEntries struct {
-	ReplayMap map[time.Time]ReplayCacheEntry
+type clientEntries struct {
+	ReplayMap map[time.Time]replayCacheEntry
 	SeqNumber int
 	SeqNumber int
 	SubKey    types.EncryptionKey
 	SubKey    types.EncryptionKey
 }
 }
 
 
 // Cache entry tracking client time values of tickets sent to the service.
 // Cache entry tracking client time values of tickets sent to the service.
-type ReplayCacheEntry struct {
+type replayCacheEntry struct {
 	PresentedTime time.Time
 	PresentedTime time.Time
 	SName         types.PrincipalName
 	SName         types.PrincipalName
 	CTime         time.Time // This combines the ticket's CTime and Cusec
 	CTime         time.Time // This combines the ticket's CTime and Cusec
@@ -70,7 +70,7 @@ func GetReplayCache(d time.Duration) *ServiceCache {
 func (c *ServiceCache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
 func (c *ServiceCache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
 	ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
 	ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
 	if ce, ok := (*c)[a.CName.GetPrincipalNameString()]; ok {
 	if ce, ok := (*c)[a.CName.GetPrincipalNameString()]; ok {
-		ce.ReplayMap[ct] = ReplayCacheEntry{
+		ce.ReplayMap[ct] = replayCacheEntry{
 			PresentedTime: time.Now().UTC(),
 			PresentedTime: time.Now().UTC(),
 			SName:         sname,
 			SName:         sname,
 			CTime:         ct,
 			CTime:         ct,
@@ -78,8 +78,8 @@ func (c *ServiceCache) AddEntry(sname types.PrincipalName, a types.Authenticator
 		ce.SeqNumber = a.SeqNumber
 		ce.SeqNumber = a.SeqNumber
 		ce.SubKey = a.SubKey
 		ce.SubKey = a.SubKey
 	} else {
 	} else {
-		(*c)[a.CName.GetPrincipalNameString()] = ClientEntries{
-			ReplayMap: map[time.Time]ReplayCacheEntry{
+		(*c)[a.CName.GetPrincipalNameString()] = clientEntries{
+			ReplayMap: map[time.Time]replayCacheEntry{
 				ct: {
 				ct: {
 					PresentedTime: time.Now().UTC(),
 					PresentedTime: time.Now().UTC(),
 					SName:         sname,
 					SName:         sname,

+ 7 - 84
service/http.go

@@ -5,27 +5,21 @@ import (
 	"encoding/base64"
 	"encoding/base64"
 	"fmt"
 	"fmt"
 	"github.com/jcmturner/gokrb5/GSSAPI"
 	"github.com/jcmturner/gokrb5/GSSAPI"
-	"github.com/jcmturner/gokrb5/crypto"
-	"github.com/jcmturner/gokrb5/iana/errorcode"
-	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/keytab"
 	"github.com/jcmturner/gokrb5/keytab"
-	"github.com/jcmturner/gokrb5/messages"
-	"github.com/jcmturner/gokrb5/types"
 	"log"
 	"log"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
-	"time"
 )
 )
 
 
 const (
 const (
 	// The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
 	// The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
 	SPNEGO_NegTokenResp_Krb_Accept_Completed = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
 	SPNEGO_NegTokenResp_Krb_Accept_Completed = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
 	// The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
 	// The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
-	SPNEGO_NegTokenResp_Reject               = "Negotiate oQcwBaADCgEC"
+	SPNEGO_NegTokenResp_Reject = "Negotiate oQcwBaADCgEC"
 )
 )
 
 
 // Kerberos SPNEGO authentication HTTP handler wrapper.
 // Kerberos SPNEGO authentication HTTP handler wrapper.
-func SPNEGOKRB5Authenticate(f http.Handler, ktab keytab.Keytab, l *log.Logger) http.Handler {
+func SPNEGOKRB5Authenticate(f http.Handler, kt keytab.Keytab, l *log.Logger) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
 		s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
 		if len(s) != 2 || s[0] != "Negotiate" {
 		if len(s) != 2 || s[0] != "Negotiate" {
@@ -59,30 +53,14 @@ func SPNEGOKRB5Authenticate(f http.Handler, ktab keytab.Keytab, l *log.Logger) h
 			rejectSPNEGO(w, l, fmt.Sprintf("%v - MechToken does not contain an AP_REQ - KRB_AP_ERR_MSG_TYPE", r.RemoteAddr))
 			rejectSPNEGO(w, l, fmt.Sprintf("%v - MechToken does not contain an AP_REQ - KRB_AP_ERR_MSG_TYPE", r.RemoteAddr))
 			return
 			return
 		}
 		}
-		err = mt.APReq.Ticket.DecryptEncPart(ktab)
-		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error decrypting the service ticket provided: %v", r.RemoteAddr, err))
-			return
-		}
-		ab, err := crypto.DecryptEncPart(mt.APReq.Authenticator, mt.APReq.Ticket.DecryptedEncPart.Key, keyusage.AP_REQ_AUTHENTICATOR)
-		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error decrypting the authenticator provided: %v", r.RemoteAddr, err))
-			return
-		}
-		var a types.Authenticator
-		err = a.Unmarshal(ab)
-		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error unmarshalling the authenticator: %v", r.RemoteAddr, err))
-			return
-		}
-		if ok, err := validateAPREQ(a, mt.APReq, r); ok {
-			cnameStr := a.CName.GetPrincipalNameString()
+
+		if ok, creds, err := ValidateAPREQ(mt.APReq, kt, r.RemoteAddr); ok {
 			ctx := r.Context()
 			ctx := r.Context()
-			ctx = context.WithValue(ctx, "cname", cnameStr)
-			ctx = context.WithValue(ctx, "crealm", a.CRealm)
+			ctx = context.WithValue(ctx, "cname", creds.Username)
+			ctx = context.WithValue(ctx, "crealm", creds.Realm)
 			ctx = context.WithValue(ctx, "authenticated", true)
 			ctx = context.WithValue(ctx, "authenticated", true)
 			if l != nil {
 			if l != nil {
-				l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, cnameStr, a.CRealm)
+				l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, creds.Username, creds.Realm)
 			}
 			}
 			w.Header().Set("WWW-Authenticate", SPNEGO_NegTokenResp_Krb_Accept_Completed)
 			w.Header().Set("WWW-Authenticate", SPNEGO_NegTokenResp_Krb_Accept_Completed)
 			f.ServeHTTP(w, r.WithContext(ctx))
 			f.ServeHTTP(w, r.WithContext(ctx))
@@ -93,61 +71,6 @@ func SPNEGOKRB5Authenticate(f http.Handler, ktab keytab.Keytab, l *log.Logger) h
 	})
 	})
 }
 }
 
 
-// Validate the AP_REQ provided in the SPNEGO NegTokenInit.
-func validateAPREQ(a types.Authenticator, APReq messages.APReq, r *http.Request) (bool, error) {
-	// Check CName in Authenticator is the same as that in the ticket
-	if !a.CName.Equal(APReq.Ticket.DecryptedEncPart.CName) {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket")
-		return false, err
-	}
-	if len(APReq.Ticket.DecryptedEncPart.CAddr) > 0 {
-		//The addresses in the ticket (if any) are then
-		//searched for an address matching the operating-system reported
-		//address of the client.  If no match is found or the server insists on
-		//ticket addresses but none are present in the ticket, the
-		//KRB_AP_ERR_BADADDR error is returned.
-		h, err := types.GetHostAddress(r.RemoteAddr)
-		if err != nil {
-			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, err.Error())
-			return false, err
-		}
-		if !types.HostAddressesContains(APReq.Ticket.DecryptedEncPart.CAddr, h) {
-			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "Client address not within the list contained in the service ticket")
-			return false, err
-		}
-	}
-
-	// Check the clock skew between the client and the service server
-	ct := a.CTime.Add(time.Duration(a.Cusec) * time.Microsecond)
-	t := time.Now().UTC()
-	// Hardcode 5 min max skew. May want to make this configurable
-	d := time.Duration(5) * time.Minute
-	if t.Sub(ct) > d || ct.Sub(t) > d {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("Clock skew with client too large. Greater than %v seconds", d))
-		return false, err
-	}
-
-	// Check for replay
-	rc := GetReplayCache(d)
-	if rc.IsReplay(d, APReq.Ticket.SName, a) {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "Replay detected")
-		return false, err
-	}
-
-	// Check for future tickets or invalid tickets
-	if APReq.Ticket.DecryptedEncPart.StartTime.Sub(t) > d || types.IsFlagSet(&APReq.Ticket.DecryptedEncPart.Flags, types.Invalid) {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "Service ticket provided is not yet valid")
-		return false, err
-	}
-
-	// Check for expired ticket
-	if t.Sub(APReq.Ticket.DecryptedEncPart.EndTime) > d {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "Service ticket provided has expired")
-		return false, err
-	}
-	return true, nil
-}
-
 // Set the headers for a rejected SPNEGO negotiation and return an unauthorized status code.
 // Set the headers for a rejected SPNEGO negotiation and return an unauthorized status code.
 func rejectSPNEGO(w http.ResponseWriter, l *log.Logger, logMsg string) {
 func rejectSPNEGO(w http.ResponseWriter, l *log.Logger, logMsg string) {
 	if l != nil {
 	if l != nil {

+ 19 - 6
types/Authenticator.go

@@ -7,6 +7,7 @@ import (
 	"github.com/jcmturner/gokrb5/asn1tools"
 	"github.com/jcmturner/gokrb5/asn1tools"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
+	"math/rand"
 	"time"
 	"time"
 )
 )
 
 
@@ -45,12 +46,24 @@ type Authenticator struct {
 func NewAuthenticator(realm string, cname PrincipalName) Authenticator {
 func NewAuthenticator(realm string, cname PrincipalName) Authenticator {
 	t := time.Now().UTC()
 	t := time.Now().UTC()
 	return Authenticator{
 	return Authenticator{
-		AVNO:   iana.PVNO,
-		CRealm: realm,
-		CName:  cname,
-		Cksum:  Checksum{},
-		Cusec:  int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
-		CTime:  t,
+		AVNO:      iana.PVNO,
+		CRealm:    realm,
+		CName:     cname,
+		Cksum:     Checksum{},
+		Cusec:     int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
+		CTime:     t,
+		SeqNumber: int(rand.Int31()),
+	}
+}
+
+func (a *Authenticator) GenerateSeqNumberAndSubKey(keyType, keySize int) {
+	a.SeqNumber = int(rand.Int31())
+	//Generate subkey value
+	sk := make([]byte, keySize)
+	rand.Read(sk)
+	a.SubKey = EncryptionKey{
+		KeyType:  keyType,
+		KeyValue: sk,
 	}
 	}
 }
 }
 
 

+ 0 - 27
types/KerberosFlags.go

@@ -63,33 +63,6 @@ encoding of a bit string that is declared with the "NamedBit"
 notation.
 notation.
 */
 */
 
 
-const (
-	Reserved               = 0
-	Forwardable            = 1
-	Forwarded              = 2
-	Proxiable              = 3
-	Proxy                  = 4
-	AllowPostDate          = 5
-	MayPostDate            = 5
-	PostDated              = 6
-	Invalid                = 7
-	Renewable              = 8
-	Initial                = 9
-	PreAuthent             = 10
-	HWAuthent              = 11
-	OptHardwareAuth        = 11
-	RequestAnonymous       = 12
-	TransitedPolicyChecked = 12
-	OKAsDelegate           = 13
-	EncPARep               = 15
-	Canonicalize           = 15
-	DisableTransitedCheck  = 26
-	RenewableOK            = 27
-	EncTktInSkey           = 28
-	Renew                  = 30
-	Validate               = 31
-)
-
 func NewKrbFlags() asn1.BitString {
 func NewKrbFlags() asn1.BitString {
 	f := asn1.BitString{}
 	f := asn1.BitString{}
 	f.Bytes = make([]byte, 4)
 	f.Bytes = make([]byte, 4)

+ 12 - 11
types/KerberosFlags_test.go

@@ -2,6 +2,7 @@ package types
 
 
 import (
 import (
 	"github.com/jcmturner/asn1"
 	"github.com/jcmturner/asn1"
+	"github.com/jcmturner/gokrb5/iana/flags"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"testing"
 	"testing"
 )
 )
@@ -9,26 +10,26 @@ import (
 func TestKerberosFlags_SetFlag(t *testing.T) {
 func TestKerberosFlags_SetFlag(t *testing.T) {
 	b := []byte{byte(64), byte(0), byte(0), byte(16)}
 	b := []byte{byte(64), byte(0), byte(0), byte(16)}
 	var f asn1.BitString
 	var f asn1.BitString
-	SetFlag(&f, Forwardable)
-	SetFlag(&f, RenewableOK)
+	SetFlag(&f, flags.Forwardable)
+	SetFlag(&f, flags.RenewableOK)
 	assert.Equal(t, b, f.Bytes, "Flag bytes not as expected")
 	assert.Equal(t, b, f.Bytes, "Flag bytes not as expected")
 }
 }
 
 
 func TestKerberosFlags_UnsetFlag(t *testing.T) {
 func TestKerberosFlags_UnsetFlag(t *testing.T) {
 	b := []byte{byte(64), byte(0), byte(0), byte(0)}
 	b := []byte{byte(64), byte(0), byte(0), byte(0)}
 	var f asn1.BitString
 	var f asn1.BitString
-	SetFlag(&f, Forwardable)
-	SetFlag(&f, RenewableOK)
-	UnsetFlag(&f, RenewableOK)
+	SetFlag(&f, flags.Forwardable)
+	SetFlag(&f, flags.RenewableOK)
+	UnsetFlag(&f, flags.RenewableOK)
 	assert.Equal(t, b, f.Bytes, "Flag bytes not as expected")
 	assert.Equal(t, b, f.Bytes, "Flag bytes not as expected")
 }
 }
 
 
 func TestKerberosFlags_IsFlagSet(t *testing.T) {
 func TestKerberosFlags_IsFlagSet(t *testing.T) {
 	var f asn1.BitString
 	var f asn1.BitString
-	SetFlag(&f, Forwardable)
-	SetFlag(&f, RenewableOK)
-	UnsetFlag(&f, Proxiable)
-	assert.True(t, IsFlagSet(&f, Forwardable))
-	assert.True(t, IsFlagSet(&f, RenewableOK))
-	assert.False(t, IsFlagSet(&f, Proxiable))
+	SetFlag(&f, flags.Forwardable)
+	SetFlag(&f, flags.RenewableOK)
+	UnsetFlag(&f, flags.Proxiable)
+	assert.True(t, IsFlagSet(&f, flags.Forwardable))
+	assert.True(t, IsFlagSet(&f, flags.RenewableOK))
+	assert.False(t, IsFlagSet(&f, flags.Proxiable))
 }
 }