Procházet zdrojové kódy

refactor API

refactor of API
Jonathan Turner před 7 roky
rodič
revize
a6e3edd1c8
100 změnil soubory, kde provedl 2596 přidání a 2250 odebrání
  1. 2 3
      .travis.yml
  2. 35 30
      README.md
  3. 17 15
      client/ASExchange.go
  4. 48 71
      client/TGSExchange.go
  5. 15 18
      client/cache.go
  6. 60 84
      client/client.go
  7. 66 40
      client/client_ad_integration_test.go
  8. 12 9
      client/client_dns_test.go
  9. 153 238
      client/client_integration_test.go
  10. 0 46
      client/http.go
  11. 22 9
      client/network.go
  12. 6 6
      client/passwd.go
  13. 6 6
      client/session.go
  14. 115 0
      client/session_test.go
  15. 69 0
      client/settings.go
  16. 1 1
      config/krb5conf.go
  17. 27 31
      credentials/ccache.go
  18. 10 8
      credentials/ccache_integration_test.go
  19. 19 13
      credentials/ccache_test.go
  20. 75 42
      credentials/credentials.go
  21. 5 5
      crypto/aes128-cts-hmac-sha1-96.go
  22. 2 2
      crypto/aes128-cts-hmac-sha1-96_test.go
  23. 4 4
      crypto/aes128-cts-hmac-sha256-128.go
  24. 2 2
      crypto/aes128-cts-hmac-sha256-128_test.go
  25. 5 5
      crypto/aes256-cts-hmac-sha1-96.go
  26. 2 2
      crypto/aes256-cts-hmac-sha1-96_test.go
  27. 4 4
      crypto/aes256-cts-hmac-sha384-192.go
  28. 2 2
      crypto/aes256-cts-hmac-sha384-192_test.go
  29. 1 1
      crypto/common/common.go
  30. 5 5
      crypto/crypto.go
  31. 4 4
      crypto/des3-cbc-sha1-kd.go
  32. 4 4
      crypto/rc4-hmac.go
  33. 2 2
      crypto/rfc3961/encryption.go
  34. 1 1
      crypto/rfc3961/keyDerivation.go
  35. 2 2
      crypto/rfc3962/encryption.go
  36. 1 1
      crypto/rfc3962/keyDerivation.go
  37. 1 1
      crypto/rfc4757/encryption.go
  38. 3 3
      crypto/rfc8009/encryption.go
  39. 2 2
      crypto/rfc8009/keyDerivation.go
  40. 22 24
      examples/example-AD.go
  41. 21 19
      examples/example.go
  42. 11 11
      examples/httpClient.go
  43. 8 7
      examples/httpServer.go
  44. 0 36
      gssapi/ContextFlags.go
  45. 8 8
      gssapi/MICToken.go
  46. 6 6
      gssapi/MICToken_test.go
  47. 0 9
      gssapi/MechType.go
  48. 0 149
      gssapi/NegotiationToken.go
  49. 25 0
      gssapi/contextFlags.go
  50. 179 82
      gssapi/gssapi.go
  51. 11 78
      gssapi/gssapi_test.go
  52. 10 10
      gssapi/wrapToken.go
  53. 7 7
      gssapi/wrapToken_test.go
  54. 6 0
      iana/flags/constants.go
  55. 1 1
      kadmin/changepasswddata.go
  56. 5 6
      kadmin/changepasswddata_test.go
  57. 2 2
      kadmin/message.go
  58. 6 7
      kadmin/message_test.go
  59. 4 4
      kadmin/passwd.go
  60. 28 29
      keytab/keytab.go
  61. 12 10
      keytab/keytab_test.go
  62. 4 4
      messages/APRep.go
  63. 12 15
      messages/APRep_test.go
  64. 106 36
      messages/APReq.go
  65. 10 12
      messages/APReq_test.go
  66. 16 16
      messages/KDCRep.go
  67. 30 35
      messages/KDCRep_test.go
  68. 13 13
      messages/KDCReq.go
  69. 43 55
      messages/KDCReq_test.go
  70. 7 7
      messages/KRBCred.go
  71. 14 17
      messages/KRBCred_test.go
  72. 6 6
      messages/KRBError.go
  73. 11 13
      messages/KRBError_test.go
  74. 8 8
      messages/KRBPriv.go
  75. 26 33
      messages/KRBPriv_test.go
  76. 4 4
      messages/KRBSafe.go
  77. 10 12
      messages/KRBSafe_test.go
  78. 53 39
      messages/Ticket.go
  79. 32 30
      messages/Ticket_test.go
  80. 7 7
      pac/client_claims_test.go
  81. 2 2
      pac/client_info_test.go
  82. 3 3
      pac/credentials_info.go
  83. 4 4
      pac/kerb_validation_info_test.go
  84. 17 75
      pac/pac_type.go
  85. 16 10
      pac/pac_type_test.go
  86. 1 1
      pac/signature_data.go
  87. 4 4
      pac/signature_data_test.go
  88. 2 2
      pac/upn_dns_info_test.go
  89. 20 66
      service/APExchange.go
  90. 65 58
      service/APExchange_test.go
  91. 17 110
      service/authenticator.go
  92. 1 1
      service/authenticator_test.go
  93. 3 3
      service/cache.go
  94. 0 83
      service/http.go
  95. 136 0
      service/settings.go
  96. 141 0
      spnego/http.go
  97. 110 147
      spnego/http_test.go
  98. 80 46
      spnego/krb5Token.go
  99. 52 46
      spnego/krb5Token_test.go
  100. 328 0
      spnego/negotiationToken.go

+ 2 - 3
.travis.yml

@@ -1,12 +1,11 @@
 language: go
 
 go:
-  - 1.9.x
   - 1.10.x
   - 1.11.x
   - master
 
-go_import_path: gopkg.in/jcmturner/gokrb5.v6
+go_import_path: gopkg.in/jcmturner/gokrb5.v7
 
 sudo: required
 
@@ -30,7 +29,7 @@ before_script:
   - sudo sed -i 's/nameserver .*/nameserver 127.0.0.1/g' /etc/resolv.conf
 
 env:
-  - TEST_KDC_ADDR=127.0.0.1 TEST_HTTP_URL="http://host.test.gokrb5" DNSUTILS_OVERRIDE_NS="127.0.0.1:53" DEBIAN_FRONTEND=noninteractive
+  - INTEGRATION=1 TESTPRIVILEGED=1 TEST_KDC_ADDR=127.0.0.1 TEST_HTTP_URL="http://host.test.gokrb5" DNSUTILS_OVERRIDE_NS="127.0.0.1:53" DEBIAN_FRONTEND=noninteractive
 
 script:
   - test -z $(gofmt -s -d -l -e $GO_FILES | tee /dev/fd/2 | xargs | sed 's/\s//g') # Fail if a .go file hasn't been formatted with gofmt

+ 35 - 30
README.md

@@ -6,7 +6,8 @@
 #### Go Version Support
 ![Go version](https://img.shields.io/badge/Go-1.11-brightgreen.svg)
 ![Go version](https://img.shields.io/badge/Go-1.10-brightgreen.svg)
-![Go version](https://img.shields.io/badge/Go-1.9-brightgreen.svg)
+
+gokrb5 may work with other versions of Go but they are not tested.
 
 ### Go Get
 To get the package, execute:
@@ -15,7 +16,7 @@ go get -d gopkg.in/jcmturner/gokrb5.v6/...
 ```
 To import this package, add the following line to your code:
 ```go
-import "gopkg.in/jcmturner/gokrb5.v6/<sub package>"
+import "gopkg.in/jcmturner/gokrb5.v7/<sub package>"
 ```
 
 ## Features
@@ -62,7 +63,7 @@ If you are interested in contributing to gokrb5, great! Please read the [contrib
 The gokrb5 libraries use the same krb5.conf configuration file format as MIT Kerberos, described [here](https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html).
 Config instances can be created by loading from a file path or by passing a string, io.Reader or bufio.Scanner to the relevant method:
 ```go
-import "gopkg.in/jcmturner/gokrb5.v6/config"
+import "gopkg.in/jcmturner/gokrb5.v7/config"
 cfg, err := config.Load("/path/to/config/file")
 cfg, err := config.NewConfigFromString(krb5Str) //String must have appropriate newline separations
 cfg, err := config.NewConfigFromReader(reader)
@@ -71,7 +72,7 @@ cfg, err := config.NewConfigFromScanner(scanner)
 ### Keytab files
 Standard keytab files can be read from a file or from a slice of bytes:
 ```go
-import 	"gopkg.in/jcmturner/gokrb5.v6/keytab"
+import 	"gopkg.in/jcmturner/gokrb5.v7/keytab"
 ktFromFile, err := keytab.Load("/path/to/file.keytab")
 ktFromBytes, err := keytab.Parse(b)
 
@@ -80,24 +81,22 @@ ktFromBytes, err := keytab.Parse(b)
 ---
 
 ### Kerberos Client
-Create a client instance with either a password or a keytab:
-```go
-import 	"gopkg.in/jcmturner/gokrb5.v6/client"
-cl := client.NewClientWithPassword("username", "REALM.COM", "password")
-cl := client.NewClientWithKeytab("username", "REALM.COM", kt)
-
-```
-Provide configuration to the client:
+**Create** a client instance with either a password or a keytab.
+A configuration must also be passed. Additionally optional additional settings can be provided.
 ```go
-cl.WithConfig(cfg)
+import 	"gopkg.in/jcmturner/gokrb5.v7/client"
+cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg)
+cl := client.NewClientWithKeytab("username", "REALM.COM", kt, cfg)
 ```
-Login:
+Optional settings are provided using the functions defined in the ``client/settings.go`` source file.
+
+**Login**:
 ```go
 err := cl.Login()
 ```
 Kerberos Ticket Granting Tickets (TGT) will be automatically renewed unless the client was created from a CCache.
 
-A client can be destroyed with the following method:
+A client can be **destroyed** with the following method:
 ```go
 cl.Destroy()
 ```
@@ -106,9 +105,10 @@ cl.Destroy()
 Active Directory does not commonly support FAST negotiation so you will need to disable this on the client.
 If this is the case you will see this error:
 ```KDC did not respond appropriately to FAST negotiation```
-To resolve this disable PA-FX-Fast on the client before performing Login() with the line below.
+To resolve this disable PA-FX-Fast on the client before performing Login().
+This is done with one of the optional client settings as shown below:
 ```go
-cl.GoKrb5Conf.DisablePAFXFast = true
+cl := client.NewClientWithPassword("username", "REALM.COM", "password", cfg, client.DisablePAFXFAST(true))
 ```
 
 #### Authenticate to a Service
@@ -117,8 +117,7 @@ cl.GoKrb5Conf.DisablePAFXFast = true
 Create the HTTP request object and then call the client's SetSPNEGOHeader method passing the Service Principal Name (SPN) or to auto generate the SPN from the request object pass a null string ""
 ```go
 r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil)
-spn := ""
-cl.SetSPNEGOHeader(r, spn)
+err := SetSPNEGOHeader(&cl, r, "") // If "" is provided for the last argument the SPN will be derived from the URL.
 HTTPResp, err := http.DefaultClient.Do(r)
 ```
 
@@ -212,21 +211,27 @@ h := http.HandlerFunc(apphandler)
 ```
 Configure the HTTP handler:
 ```go
-c := service.NewConfig(kt)
-http.Handler("/", service.SPNEGOKRB5Authenticate(h, c, l))
+http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l)))
 ```
-The serviceAccountName needs to be defined when using Active Directory where the SPN is mapped to a user account.
-If this is not required it should be set to an empty string "".
+The handler to be wrapped and the keytab are required arguments. 
+Additional optional settings can be provided, such as the logger shown above.
+
+Another example of optional settings may be that when using Active Directory where the SPN is mapped to a user account 
+the keytab may contain an entry for this user account. In this case this should be specified as below with the ``KeytabPrincipal``:
+```go
+http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.KeytabPrincipal(pn)))
+```
+
 If authentication succeeds then the request's context will have the following values added so they can be accessed within the application's handler:
-* service.CTXKeyAuthenticated - Boolean indicating if the user is authenticated. Use of this value should also handle that this value may not be set and should assume "false" in that case.
-* service.CTXKeyCredentials - The authenticated user's credentials.
+* spnego.CTXKeyAuthenticated - Boolean indicating if the user is authenticated. Use of this value should also handle that this value may not be set and should assume "false" in that case.
+* spnego.CTXKeyCredentials - The authenticated user's credentials.
 If Microsoft Active Directory is used as the KDC then additional ADCredentials are available in the credentials.Attributes map under the key credentials.AttributeKeyADCredentials. For example the SIDs of the users group membership are available and can be used by your application for authorization.
 
 Access the credentials within your application:
 ```go
 ctx := r.Context()
-if validuser, ok := ctx.Value(service.CTXKeyAuthenticated).(bool); ok && validuser {
-        if creds, ok := ctx.Value(service.CTXKeyCredentials).(goidentity.Identity); ok {
+if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser {
+        if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok {
                 if ADCreds, ok := creds.Attributes()[credentials.AttributeKeyADCredentials].(credentials.ADCredentials); ok {
                         // Now access the fields of the ADCredentials struct. For example:
                         groupSids := ADCreds.GroupMembershipSIDs
@@ -239,9 +244,9 @@ if validuser, ok := ctx.Value(service.CTXKeyAuthenticated).(bool); ok && validus
 #### Generic Kerberised Service - Validating Client Details
 To validate the AP_REQ sent by the client on the service side call this method:
 ```go
-import 	"gopkg.in/jcmturner/gokrb5.v6/service"
-a := service.NewSPNEGOAuthenticator(kt)
-if ok, creds, err := service.ValidateAPREQ(mt.APReq, a); ok {
+import 	"gopkg.in/jcmturner/gokrb5.v7/service"
+s := service.NewSettings(&kt) // kt is a keytab and optional settings can also be provided.
+if ok, creds, err := service.VerifyAPREQ(APReq, s); ok {
         // Perform application specific actions
         // creds object has details about the client identity
 }

+ 17 - 15
client/ASExchange.go

@@ -1,14 +1,14 @@
 package client
 
 import (
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // ASExchange performs an AS exchange for the client to retrieve a TGT.
@@ -29,7 +29,7 @@ func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (
 			switch e.ErrorCode {
 			case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
 				// From now on assume this client will need to do this pre-auth and set the PAData
-				cl.GoKrb5Conf.AssumePAEncTimestampRequired = true
+				cl.settings.assumePreAuthentication = true
 				err = setPAData(cl, e, &ASReq)
 				if err != nil {
 					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
@@ -63,25 +63,26 @@ func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (
 	if err != nil {
 		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
 	}
-	if ok, err := ASRep.IsValid(cl.Config, cl.Credentials, ASReq); !ok {
+	if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
 		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
 	}
 	return ASRep, nil
 }
 
+// setPAData adds pre-authentication data to the AS_REQ.
 func setPAData(cl *Client, krberr messages.KRBError, ASReq *messages.ASReq) error {
-	if !cl.GoKrb5Conf.DisablePAFXFast {
+	if !cl.settings.DisablePAFXFAST() {
 		pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
 		ASReq.PAData = append(ASReq.PAData, pa)
 	}
-	if cl.GoKrb5Conf.AssumePAEncTimestampRequired {
+	if cl.settings.AssumePreAuthentication() {
 		paTSb, err := types.GetPAEncTSEncAsnMarshalled()
 		if err != nil {
 			return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
 		}
 		var et etype.EType
 		if krberr.ErrorCode == 0 {
-			etn := cl.GoKrb5Conf.preAuthEType
+			etn := cl.settings.preAuthEType
 			if etn == 0 {
 				etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0])
 			}
@@ -96,9 +97,9 @@ func setPAData(cl *Client, krberr messages.KRBError, ASReq *messages.ASReq) erro
 			if err != nil {
 				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
 			}
-			cl.GoKrb5Conf.preAuthEType = et.GetETypeID()
+			cl.settings.preAuthEType = et.GetETypeID()
 		}
-		key, err := cl.Key(et, krberr)
+		key, err := cl.Key(et, &krberr)
 		if err != nil {
 			return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
 		}
@@ -119,6 +120,7 @@ func setPAData(cl *Client, krberr messages.KRBError, ASReq *messages.ASReq) erro
 	return nil
 }
 
+// preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
 func preAuthEType(krberr messages.KRBError) (etype etype.EType, err error) {
 	//The preferred ordering of the "hint" pre-authentication data that
 	//affect client key selection is: ETYPE-INFO2, followed by ETYPE-INFO,

+ 48 - 71
client/TGSExchange.go

@@ -1,96 +1,82 @@
 package client
 
 import (
-	"gopkg.in/jcmturner/gokrb5.v6/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
-// TGSExchange performs a TGS exchange to retrieve a ticket to the specified SPN.
-// The ticket retrieved is added to the client's cache.
-func (cl *Client) TGSExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool, referral int) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
-	tgsReq, err = messages.NewTGSReq(cl.Credentials.CName, kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
+// TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN.
+func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
+	tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
 	if err != nil {
 		return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ")
 	}
-	tgsRep, err = cl.tgsExchange(tgsReq, kdcRealm, sessionKey)
+	return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0)
+}
+
+// TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP.
+// Referrals are automatically handled.
+// The client's cache is updated with the ticket received.
+func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
+	var tgsRep messages.TGSRep
+	b, err := tgsReq.Marshal()
 	if err != nil {
-		return
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ")
 	}
-	// TODO should this check the first element is krbtgt rather than the nametype?
-	if tgsRep.Ticket.SName.NameType == nametype.KRB_NT_SRV_INST && !tgsRep.Ticket.SName.Equal(spn) {
-		if referral > 5 {
-			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of referrals exceeded")
+	r, err := cl.sendToKDC(b, kdcRealm)
+	if err != nil {
+		if _, ok := err.(messages.KRBError); ok {
+			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString())
 		}
-		// Server referral https://tools.ietf.org/html/rfc6806.html#section-8
-		// The TGS Rep contains a TGT for another domain as the service resides in that domain.
-		cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
-		realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
-		referral++
-		return cl.TGSExchange(spn, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, false, referral)
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
 	}
-	return
-}
-
-// TGSREQ exchanges the provides TGS_REQ with the KDC to retrieve a TGS_REP
-func (cl *Client) TGSREQ(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
-	tgsRep, err := cl.tgsExchange(tgsReq, kdcRealm, sessionKey)
+	err = tgsRep.Unmarshal(r)
 	if err != nil {
-		return tgsReq, tgsRep, err
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
 	}
-	// TODO should this check the first element is krbtgt rather than the nametype?
-	if tgsRep.Ticket.SName.NameType == nametype.KRB_NT_SRV_INST && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
+	err = tgsRep.DecryptEncPart(sessionKey)
+	if err != nil {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
+	}
+	if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
+	}
+
+	if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
 		if referral > 5 {
-			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of referrals exceeded")
+			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded")
 		}
 		// Server referral https://tools.ietf.org/html/rfc6806.html#section-8
 		// The TGS Rep contains a TGT for another domain as the service resides in that domain.
-		cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+		cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
 		realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
 		referral++
 		if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 {
-			tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName, kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
+			tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
 			if err != nil {
 				return tgsReq, tgsRep, err
 			}
 		}
-		tgsReq, err = messages.NewTGSReq(cl.Credentials.CName, kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal)
+		tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal)
 		if err != nil {
 			return tgsReq, tgsRep, err
 		}
-		return cl.TGSREQ(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
+		return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
 	}
+	cl.cache.addEntry(
+		tgsRep.Ticket,
+		tgsRep.DecryptedEncPart.AuthTime,
+		tgsRep.DecryptedEncPart.StartTime,
+		tgsRep.DecryptedEncPart.EndTime,
+		tgsRep.DecryptedEncPart.RenewTill,
+		tgsRep.DecryptedEncPart.Key,
+	)
 	return tgsReq, tgsRep, err
 }
 
-func (cl *Client) tgsExchange(tgsReq messages.TGSReq, kdcRealm string, sessionKey types.EncryptionKey) (tgsRep messages.TGSRep, err error) {
-	b, err := tgsReq.Marshal()
-	if err != nil {
-		return tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to generate a new TGS_REQ")
-	}
-	r, err := cl.sendToKDC(b, kdcRealm)
-	if err != nil {
-		if _, ok := err.(messages.KRBError); ok {
-			return tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC")
-		}
-		return tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
-	}
-	err = tgsRep.Unmarshal(r)
-	if err != nil {
-		return tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
-	}
-	err = tgsRep.DecryptEncPart(sessionKey)
-	if err != nil {
-		return tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
-	}
-	if ok, err := tgsRep.IsValid(cl.Config, tgsReq); !ok {
-		return tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
-	}
-	return
-}
-
 // GetServiceTicket makes a request to get a service ticket for the SPN specified
 // SPN format: <SERVICE>/<FQDN> Eg. HTTP/www.example.com
 // The ticket will be added to the client's ticket cache
@@ -108,18 +94,9 @@ func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.Encryptio
 	if err != nil {
 		return tkt, skey, err
 	}
-
-	_, tgsRep, err := cl.TGSExchange(princ, realm, tgt, skey, false, 0)
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false)
 	if err != nil {
 		return tkt, skey, err
 	}
-	cl.cache.addEntry(
-		tgsRep.Ticket,
-		tgsRep.DecryptedEncPart.AuthTime,
-		tgsRep.DecryptedEncPart.StartTime,
-		tgsRep.DecryptedEncPart.EndTime,
-		tgsRep.DecryptedEncPart.RenewTill,
-		tgsRep.DecryptedEncPart.Key,
-	)
 	return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil
 }

+ 15 - 18
client/cache.go

@@ -1,20 +1,21 @@
 package client
 
 import (
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
-	"strings"
+	"errors"
 	"sync"
 	"time"
+
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
-// Cache for client tickets.
+// Cache for service tickets held by the client.
 type Cache struct {
 	Entries map[string]CacheEntry
 	mux     sync.RWMutex
 }
 
-// CacheEntry holds details for a client cache entry.
+// CacheEntry holds details for a cache entry.
 type CacheEntry struct {
 	Ticket     messages.Ticket
 	AuthTime   time.Time
@@ -31,7 +32,7 @@ func NewCache() *Cache {
 	}
 }
 
-// GetEntry returns a cache entry that matches the SPN.
+// getEntry returns a cache entry that matches the SPN.
 func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
 	c.mux.RLock()
 	defer c.mux.RUnlock()
@@ -39,9 +40,9 @@ func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
 	return e, ok
 }
 
-// AddEntry adds a ticket to the cache.
+// addEntry adds a ticket to the cache.
 func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
-	spn := strings.Join(tkt.SName.NameString, "/")
+	spn := tkt.SName.PrincipalNameString()
 	c.mux.Lock()
 	defer c.mux.Unlock()
 	(*c).Entries[spn] = CacheEntry{
@@ -55,7 +56,7 @@ func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, rene
 	return c.Entries[spn]
 }
 
-// Clear deletes all the cache entries
+// clear deletes all the cache entries
 func (c *Cache) clear() {
 	c.mux.Lock()
 	defer c.mux.Unlock()
@@ -95,17 +96,13 @@ func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.Encryption
 // To renew from outside the client package use GetCachedTicket
 func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
 	spn := e.Ticket.SName
-	_, tgsRep, err := cl.TGSExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true, 0)
+	_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
 	if err != nil {
 		return e, err
 	}
-	e = cl.cache.addEntry(
-		tgsRep.Ticket,
-		tgsRep.DecryptedEncPart.AuthTime,
-		tgsRep.DecryptedEncPart.StartTime,
-		tgsRep.DecryptedEncPart.EndTime,
-		tgsRep.DecryptedEncPart.RenewTill,
-		tgsRep.DecryptedEncPart.Key,
-	)
+	e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
+	if !ok {
+		return e, errors.New("ticket was not added to cache")
+	}
 	return e, nil
 }

+ 60 - 84
client/client.go

@@ -6,44 +6,35 @@ import (
 	"fmt"
 	"time"
 
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // Client side configuration and state.
 type Client struct {
 	Credentials *credentials.Credentials
 	Config      *config.Config
-	GoKrb5Conf  Config
+	settings    *Settings
 	sessions    *sessions
 	cache       *Cache
 }
 
-// Config struct holds GoKRB5 specific client configurations.
-// Set Disable_PA_FX_FAST to true to force this behaviour off.
-// 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.
-type Config struct {
-	DisablePAFXFast              bool
-	AssumePAEncTimestampRequired bool
-	preAuthEType                 int32
-}
-
 // NewClientWithPassword creates a new client from a password credential.
 // Set the realm to empty string to use the default realm from config.
-func NewClientWithPassword(username, realm, password string) Client {
-	creds := credentials.NewCredentials(username, realm)
-	return Client{
+func NewClientWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+	creds := credentials.New(username, realm)
+	return &Client{
 		Credentials: creds.WithPassword(password),
-		Config:      config.NewConfig(),
-		GoKrb5Conf:  Config{},
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
 		sessions: &sessions{
 			Entries: make(map[string]*session),
 		},
@@ -52,12 +43,12 @@ func NewClientWithPassword(username, realm, password string) Client {
 }
 
 // NewClientWithKeytab creates a new client from a keytab credential.
-func NewClientWithKeytab(username, realm string, kt keytab.Keytab) Client {
-	creds := credentials.NewCredentials(username, realm)
-	return Client{
+func NewClientWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+	creds := credentials.New(username, realm)
+	return &Client{
 		Credentials: creds.WithKeytab(kt),
-		Config:      config.NewConfig(),
-		GoKrb5Conf:  Config{},
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
 		sessions: &sessions{
 			Entries: make(map[string]*session),
 		},
@@ -68,11 +59,11 @@ func NewClientWithKeytab(username, realm string, kt keytab.Keytab) Client {
 // NewClientFromCCache create a client from a populated client cache.
 //
 // WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
-func NewClientFromCCache(c credentials.CCache) (Client, error) {
-	cl := Client{
+func NewClientFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) {
+	cl := &Client{
 		Credentials: c.GetClientCredentials(),
-		Config:      config.NewConfig(),
-		GoKrb5Conf:  Config{},
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
 		sessions: &sessions{
 			Entries: make(map[string]*session),
 		},
@@ -117,30 +108,14 @@ func NewClientFromCCache(c credentials.CCache) (Client, error) {
 	return cl, nil
 }
 
-// WithConfig sets the Kerberos configuration for the client.
-func (cl *Client) WithConfig(cfg *config.Config) *Client {
-	cl.Config = cfg
-	return cl
-}
-
-// WithKeytab adds a keytab to the client
-func (cl *Client) WithKeytab(kt keytab.Keytab) *Client {
-	cl.Credentials.WithKeytab(kt)
-	return cl
-}
-
-// WithPassword adds a password to the client
-func (cl *Client) WithPassword(password string) *Client {
-	cl.Credentials.WithPassword(password)
-	return cl
-}
-
-// Key returns a key for the client. Preferably from a keytab and then generated from the password.
-// The KRBError would have been returned from the KDC and must be of type KDC_ERR_PREAUTH_REQUIRED.
-// If a KRBError is not available pass messages.KRBError{} and a key will be returned from the credentials keytab.
-func (cl *Client) Key(etype etype.EType, krberr messages.KRBError) (types.EncryptionKey, error) {
+// Key returns the client's encryption key for the specified encryption type.
+// The key can be retrieved either from the keytab or generated from the client's password.
+// If the client has both a keytab and a password defined the keytab is favoured as the source for the key
+// A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive
+// the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument.
+func (cl *Client) Key(etype etype.EType, krberr *messages.KRBError) (types.EncryptionKey, error) {
 	if cl.Credentials.HasKeytab() && etype != nil {
-		return cl.Credentials.Keytab.GetEncryptionKey(cl.Credentials.CName.NameString, cl.Credentials.Realm, 0, etype.GetETypeID())
+		return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), 0, etype.GetETypeID())
 	} else if cl.Credentials.HasPassword() {
 		if krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
 			var pas types.PADataSequence
@@ -148,43 +123,33 @@ func (cl *Client) Key(etype etype.EType, krberr messages.KRBError) (types.Encryp
 			if err != nil {
 				return types.EncryptionKey{}, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
 			}
-			key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
+			key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
 			return key, err
 		}
-		key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password, cl.Credentials.CName, cl.Credentials.Realm, etype.GetETypeID(), types.PADataSequence{})
+		key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})
 		return key, err
 	}
 	return types.EncryptionKey{}, errors.New("credential has neither keytab or password to generate key")
 }
 
-// LoadConfig loads the Kerberos configuration for the client from file path specified.
-func (cl *Client) LoadConfig(cfgPath string) (*Client, error) {
-	cfg, err := config.Load(cfgPath)
-	if err != nil {
-		return cl, err
-	}
-	cl.Config = cfg
-	return cl, nil
-}
-
 // IsConfigured indicates if the client has the values required set.
 func (cl *Client) IsConfigured() (bool, error) {
-	if cl.Credentials.Username == "" {
+	if cl.Credentials.UserName() == "" {
 		return false, errors.New("client does not have a username")
 	}
-	if cl.Credentials.Realm == "" {
+	if cl.Credentials.Domain() == "" {
 		return false, errors.New("client does not have a define realm")
 	}
 	// Client needs to have either a password, keytab or a session already (later when loading from CCache)
 	if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
-		authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Realm)
+		authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
 		if err != nil || authTime.IsZero() {
 			return false, errors.New("client has neither a keytab nor a password set and no session")
 		}
 	}
 	if !cl.Config.LibDefaults.DNSLookupKDC {
 		for _, r := range cl.Config.Realms {
-			if r.Realm == cl.Credentials.Realm {
+			if r.Realm == cl.Credentials.Domain() {
 				if len(r.KDC) > 0 {
 					return true, nil
 				}
@@ -200,7 +165,18 @@ func (cl *Client) Login() error {
 	if ok, err := cl.IsConfigured(); !ok {
 		return err
 	}
-	ASReq, err := messages.NewASReqForTGT(cl.Credentials.Realm, cl.Config, cl.Credentials.CName)
+	if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
+		_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+		if err != nil {
+			return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session")
+		}
+		if time.Now().UTC().After(endTime) {
+			return krberror.NewKrberror(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session")
+		}
+		// no credentials but there is a session with tgt already
+		return nil
+	}
+	ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
 	if err != nil {
 		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
 	}
@@ -208,27 +184,27 @@ func (cl *Client) Login() error {
 	if err != nil {
 		return krberror.Errorf(err, krberror.KRBMsgError, "failed setting AS_REQ PAData")
 	}
-	ASRep, err := cl.ASExchange(cl.Credentials.Realm, ASReq, 0)
+	ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
 	if err != nil {
 		return err
 	}
-	cl.AddSession(ASRep.Ticket, ASRep.DecryptedEncPart)
+	cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart)
 	return nil
 }
 
-// remoteRealmSession returns the session for a realm that the client is not a member of but for which there is a trust
+// realmLogin obtains or renews a TGT and establishes a session for the realm specified.
 func (cl *Client) realmLogin(realm string) error {
-	if realm == cl.Credentials.Realm {
+	if realm == cl.Credentials.Domain() {
 		return cl.Login()
 	}
-	_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Realm)
+	_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
 	if err != nil || time.Now().UTC().After(endTime) {
 		err := cl.Login()
 		if err != nil {
 			return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
 		}
 	}
-	tgt, skey, err := cl.sessionTGT(cl.Credentials.Realm)
+	tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain())
 	if err != nil {
 		return err
 	}
@@ -238,19 +214,19 @@ func (cl *Client) realmLogin(realm string) error {
 		NameString: []string{"krbtgt", realm},
 	}
 
-	_, tgsRep, err := cl.TGSExchange(spn, cl.Credentials.Realm, tgt, skey, false, 0)
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false)
 	if err != nil {
 		return err
 	}
-	cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+	cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
 
 	return nil
 }
 
 // Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
 func (cl *Client) Destroy() {
-	creds := credentials.NewCredentials("", "")
+	creds := credentials.New("", "")
 	cl.sessions.destroy()
 	cl.cache.clear()
-	cl.Credentials = &creds
+	cl.Credentials = creds
 }

+ 66 - 40
client/client_ad_integration_test.go

@@ -1,25 +1,31 @@
-// +build adintegration
-// To turn on this test use -tags=integration in go test command
-
 package client
 
 import (
+	"bytes"
 	"encoding/hex"
+	"log"
+
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
+
 	"testing"
 )
 
 func TestClient_SuccessfulLogin_AD(t *testing.T) {
+	test.AD(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -28,12 +34,14 @@ func TestClient_SuccessfulLogin_AD(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket_AD(t *testing.T) {
+	test.AD(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -44,32 +52,38 @@ func TestClient_GetServiceTicket_AD(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	assert.Equal(t, int32(18), key.KeyType)
 
 	b, _ = hex.DecodeString(testdata.SYSHTTP_KEYTAB)
-	skt, _ := keytab.Parse(b)
-	err = tkt.DecryptEncPart(skt, "sysHTTP")
+	skt := keytab.New()
+	skt.Unmarshal(b)
+	sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}}
+	err = tkt.DecryptEncPart(skt, &sname)
 	if err != nil {
 		t.Errorf("could not decrypt service ticket: %v", err)
 	}
-	isPAC, pac, e := tkt.GetPACType(skt, "sysHTTP")
-	if e != nil {
-		t.Errorf("error getting PAC: %v", e)
+	w := bytes.NewBufferString("")
+	l := log.New(w, "", 0)
+	isPAC, pac, err := tkt.GetPACType(skt, &sname, l)
+	if err != nil {
+		t.Log(w.String())
+		t.Errorf("error getting PAC: %v", err)
 	}
 	assert.True(t, isPAC, "should have PAC")
-	assert.Equal(t, "TEST.GOKRB5", pac.KerbValidationInfo.LogonDomainName.String(), "domain name in PAC not correct")
+	assert.Equal(t, "TEST", pac.KerbValidationInfo.LogonDomainName.String(), "domain name in PAC not correct")
 }
 
 func TestClient_SuccessfulLogin_AD_TRUST_USER_DOMAIN(t *testing.T) {
+	test.AD(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD_TRUST_USER_DOMAIN}
 	c.LibDefaults.DefaultRealm = "USER.GOKRB5"
-	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt)
-	cl.WithConfig(c)
-	cl.GoKrb5Conf.DisablePAFXFast = true
+	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true))
 
 	err := cl.Login()
 	if err != nil {
@@ -78,19 +92,20 @@ func TestClient_SuccessfulLogin_AD_TRUST_USER_DOMAIN(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) {
+	test.AD(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD_TRUST_USER_DOMAIN}
 	c.LibDefaults.DefaultRealm = "USER.GOKRB5"
 	c.LibDefaults.Canonicalize = true
-	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt)
 	c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"}
 	c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
 	c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"}
 	c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
-	cl.WithConfig(c)
-	cl.GoKrb5Conf.DisablePAFXFast = true
+	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true))
 
 	err := cl.Login()
 
@@ -102,17 +117,22 @@ func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType)
 
 	b, _ = hex.DecodeString(testdata.SYSHTTP_RESGOKRB5_AD_KEYTAB)
-	skt, _ := keytab.Parse(b)
-	err = tkt.DecryptEncPart(skt, "sysHTTP")
+	skt := keytab.New()
+	skt.Unmarshal(b)
+	sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}}
+	err = tkt.DecryptEncPart(skt, &sname)
 	if err != nil {
 		t.Errorf("error decrypting ticket with service keytab: %v", err)
 	}
-	isPAC, pac, err := tkt.GetPACType(skt, "sysHTTP")
+	w := bytes.NewBufferString("")
+	l := log.New(w, "", 0)
+	isPAC, pac, err := tkt.GetPACType(skt, &sname, l)
 	if err != nil {
+		t.Log(w.String())
 		t.Errorf("error getting PAC: %v", err)
 	}
 	assert.True(t, isPAC, "Did not find PAC in service ticket")
@@ -121,19 +141,20 @@ func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket_AD_USER_DOMAIN(t *testing.T) {
+	test.AD(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD_TRUST_USER_DOMAIN}
 	c.LibDefaults.DefaultRealm = "USER.GOKRB5"
 	c.LibDefaults.Canonicalize = true
-	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt)
 	c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"}
 	c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
 	c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"}
 	c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
-	cl.WithConfig(c)
-	cl.GoKrb5Conf.DisablePAFXFast = true
+	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, DisablePAFXFAST(true))
 
 	err := cl.Login()
 
@@ -145,17 +166,22 @@ func TestClient_GetServiceTicket_AD_USER_DOMAIN(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	//assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType)
 
 	b, _ = hex.DecodeString(testdata.TESTUSER2_USERKRB5_AD_KEYTAB)
-	skt, _ := keytab.Parse(b)
-	err = tkt.DecryptEncPart(skt, "testuser2")
+	skt := keytab.New()
+	skt.Unmarshal(b)
+	sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"testuser2"}}
+	err = tkt.DecryptEncPart(skt, &sname)
 	if err != nil {
 		t.Errorf("error decrypting ticket with service keytab: %v", err)
 	}
-	isPAC, pac, err := tkt.GetPACType(skt, "testuser2")
+	w := bytes.NewBufferString("")
+	l := log.New(w, "", 0)
+	isPAC, pac, err := tkt.GetPACType(skt, &sname, l)
 	if err != nil {
+		t.Log(w.String())
 		t.Errorf("error getting PAC: %v", err)
 	}
 	assert.True(t, isPAC, "Did not find PAC in service ticket")

+ 12 - 9
client/client_dns_test.go

@@ -1,17 +1,18 @@
-// +build dns
-
 package client
 
 import (
 	"encoding/hex"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 	"testing"
 )
 
 func TestResolveKDC(t *testing.T) {
+	test.Privileged(t)
+
 	//ns := os.Getenv("DNSUTILS_OVERRIDE_NS")
 	//if ns == "" {
 	//	os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS)
@@ -19,7 +20,7 @@ func TestResolveKDC(t *testing.T) {
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.LibDefaults.DNSLookupKDC = true
 	var cl Client
-	cl.WithConfig(c)
+	cl.Config = c
 	count, res, err := cl.Config.GetKDCs(c.LibDefaults.DefaultRealm, true)
 	if err != nil {
 		t.Errorf("error resolving KDC via DNS TCP: %v", err)
@@ -52,6 +53,8 @@ func TestResolveKDC(t *testing.T) {
 }
 
 func TestClient_Login_DNSKDCs(t *testing.T) {
+	test.Privileged(t)
+
 	//ns := os.Getenv("DNSUTILS_OVERRIDE_NS")
 	//if ns == "" {
 	//	os.Setenv("DNSUTILS_OVERRIDE_NS", testdata.TEST_NS)
@@ -63,9 +66,9 @@ func TestClient_Login_DNSKDCs(t *testing.T) {
 	c.Realms = []config.Realm{}
 
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {

+ 153 - 238
client/client_integration_test.go

@@ -1,13 +1,10 @@
-// +build integration
-// To turn on this test use -tags=integration in go test command
-
-package client
+package client_test
 
 import (
 	"bytes"
 	"encoding/hex"
+	"errors"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/exec"
@@ -16,44 +13,50 @@ import (
 	"testing"
 	"time"
 
-	"errors"
 	"fmt"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/spnego"
+	"gopkg.in/jcmturner/gokrb5.v7/test"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 	"strings"
 	"sync"
 )
 
 func TestClient_SuccessfulLogin_Keytab(t *testing.T) {
+	test.Integration(t)
+
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	var tests = []string{
 		testdata.TEST_KDC,
 		testdata.TEST_KDC_OLD,
 		testdata.TEST_KDC_LASTEST,
 	}
-	for _, test := range tests {
-		c.Realms[0].KDC = []string{addr + ":" + test}
-		cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-		cl.WithConfig(c)
+	for _, tst := range tests {
+		c.Realms[0].KDC = []string{addr + ":" + tst}
+		cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 		err := cl.Login()
 		if err != nil {
-			t.Errorf("error on logging in with KDC %s: %v\n", test, err)
+			t.Errorf("error on logging in with KDC %s: %v\n", tst, err)
 		}
 	}
 }
 
 func TestClient_SuccessfulLogin_Password(t *testing.T) {
+	test.Integration(t)
+
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
@@ -64,21 +67,23 @@ func TestClient_SuccessfulLogin_Password(t *testing.T) {
 		testdata.TEST_KDC_OLD,
 		testdata.TEST_KDC_LASTEST,
 	}
-	for _, test := range tests {
-		c.Realms[0].KDC = []string{addr + ":" + test}
-		cl := NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue")
-		cl.WithConfig(c)
+	for _, tst := range tests {
+		c.Realms[0].KDC = []string{addr + ":" + tst}
+		cl := client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c)
 
 		err := cl.Login()
 		if err != nil {
-			t.Errorf("error on logging in with KDC %s: %v\n", test, err)
+			t.Errorf("error on logging in with KDC %s: %v\n", tst, err)
 		}
 	}
 }
 
 func TestClient_SuccessfulLogin_TCPOnly(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
@@ -86,8 +91,7 @@ func TestClient_SuccessfulLogin_TCPOnly(t *testing.T) {
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
 	c.LibDefaults.UDPPreferenceLimit = 1
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -96,8 +100,11 @@ func TestClient_SuccessfulLogin_TCPOnly(t *testing.T) {
 }
 
 func TestClient_ASExchange_TGSExchange_EncTypes_Keytab(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
@@ -112,28 +119,29 @@ func TestClient_ASExchange_TGSExchange_EncTypes_Keytab(t *testing.T) {
 		"aes256-cts-hmac-sha384-192",
 		"rc4-hmac",
 	}
-	for _, test := range tests {
-		c.LibDefaults.DefaultTktEnctypes = []string{test}
-		c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[test]}
-		c.LibDefaults.DefaultTGSEnctypes = []string{test}
-		c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[test]}
-		cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-		cl.WithConfig(c)
+	for _, tst := range tests {
+		c.LibDefaults.DefaultTktEnctypes = []string{tst}
+		c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]}
+		c.LibDefaults.DefaultTGSEnctypes = []string{tst}
+		c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]}
+		cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 		err := cl.Login()
 		if err != nil {
-			t.Errorf("error on login using enctype %s: %v\n", test, err)
+			t.Errorf("error on login using enctype %s: %v\n", tst, err)
 		}
 		tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
 		if err != nil {
-			t.Errorf("error in TGS exchange using enctype %s: %v", test, err)
+			t.Errorf("error in TGS exchange using enctype %s: %v", tst, err)
 		}
-		assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", test)
-		assert.Equal(t, etypeID.ETypesByName[test], key.KeyType, "Key is not for enctype %s", test)
+		assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst)
+		assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst)
 	}
 }
 
 func TestClient_ASExchange_TGSExchange_EncTypes_Password(t *testing.T) {
+	test.Integration(t)
+
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
@@ -148,38 +156,39 @@ func TestClient_ASExchange_TGSExchange_EncTypes_Password(t *testing.T) {
 		"aes256-cts-hmac-sha384-192",
 		"rc4-hmac",
 	}
-	for _, test := range tests {
-		c.LibDefaults.DefaultTktEnctypes = []string{test}
-		c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[test]}
-		c.LibDefaults.DefaultTGSEnctypes = []string{test}
-		c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[test]}
-		cl := NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue")
-		cl.WithConfig(c)
+	for _, tst := range tests {
+		c.LibDefaults.DefaultTktEnctypes = []string{tst}
+		c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName[tst]}
+		c.LibDefaults.DefaultTGSEnctypes = []string{tst}
+		c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName[tst]}
+		cl := client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "passwordvalue", c)
 
 		err := cl.Login()
 		if err != nil {
-			t.Errorf("error on login using enctype %s: %v\n", test, err)
+			t.Errorf("error on login using enctype %s: %v\n", tst, err)
 		}
 		tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
 		if err != nil {
-			t.Errorf("error in TGS exchange using enctype %s: %v", test, err)
+			t.Errorf("error in TGS exchange using enctype %s: %v", tst, err)
 		}
-		assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", test)
-		assert.Equal(t, etypeID.ETypesByName[test], key.KeyType, "Key is not for enctype %s", test)
+		assert.Equal(t, "TEST.GOKRB5", tkt.Realm, "Realm in ticket not as expected for %s test", tst)
+		assert.Equal(t, etypeID.ETypesByName[tst], key.KeyType, "Key is not for enctype %s", tst)
 	}
 }
 
 func TestClient_FailedLogin(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_WRONGPASSWD)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err == nil {
@@ -188,16 +197,18 @@ func TestClient_FailedLogin(t *testing.T) {
 }
 
 func TestClient_SuccessfulLogin_UserRequiringPreAuth(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -206,8 +217,11 @@ func TestClient_SuccessfulLogin_UserRequiringPreAuth(t *testing.T) {
 }
 
 func TestClient_SuccessfulLogin_UserRequiringPreAuth_TCPOnly(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
@@ -215,8 +229,7 @@ func TestClient_SuccessfulLogin_UserRequiringPreAuth_TCPOnly(t *testing.T) {
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
 	c.LibDefaults.UDPPreferenceLimit = 1
-	cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -225,12 +238,14 @@ func TestClient_SuccessfulLogin_UserRequiringPreAuth_TCPOnly(t *testing.T) {
 }
 
 func TestClient_NetworkTimeout(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{testdata.TEST_KDC_BADADDR + ":88"}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err == nil {
@@ -239,16 +254,18 @@ func TestClient_NetworkTimeout(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -259,7 +276,7 @@ func TestClient_GetServiceTicket(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	assert.Equal(t, int32(18), key.KeyType)
 
 	//Check cache use - should get the same values back again
@@ -272,16 +289,18 @@ func TestClient_GetServiceTicket(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket_InvalidSPN(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -294,16 +313,18 @@ func TestClient_GetServiceTicket_InvalidSPN(t *testing.T) {
 }
 
 func TestClient_GetServiceTicket_OlderKDC(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_OLD}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {
@@ -314,64 +335,23 @@ func TestClient_GetServiceTicket_OlderKDC(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	assert.Equal(t, int32(18), key.KeyType)
 }
 
-func TestClient_SetSPNEGOHeader(t *testing.T) {
-	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	addr := os.Getenv("TEST_KDC_ADDR")
-	if addr == "" {
-		addr = testdata.TEST_KDC_ADDR
-	}
-	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
-
-	err := cl.Login()
-	if err != nil {
-		t.Fatalf("error on AS_REQ: %v\n", err)
-	}
-	url := os.Getenv("TEST_HTTP_URL")
-	if url == "" {
-		url = testdata.TEST_HTTP_URL
-	}
-	paths := []string{
-		"/modkerb/index.html",
-		"/modgssapi/index.html",
-	}
-	for _, p := range paths {
-		r, _ := http.NewRequest("GET", url+p, nil)
-		httpResp, err := http.DefaultClient.Do(r)
-		if err != nil {
-			t.Fatalf("%s request error: %v\n", url+p, err)
-		}
-		assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected")
-		err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
-		if err != nil {
-			t.Fatalf("error setting client SPNEGO header: %v", err)
-		}
-		httpResp, err = http.DefaultClient.Do(r)
-		if err != nil {
-			t.Fatalf("%s request error: %v\n", url+p, err)
-		}
-		assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
-	}
-}
-
 func TestMultiThreadedClientUse(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	var wg sync.WaitGroup
 	wg.Add(5)
@@ -391,7 +371,7 @@ func TestMultiThreadedClientUse(t *testing.T) {
 	for i := 0; i < 5; i++ {
 		go func() {
 			defer wg2.Done()
-			err := spnegoGet(&cl)
+			err := spnegoGet(cl)
 			if err != nil {
 				panic(err)
 			}
@@ -400,54 +380,7 @@ func TestMultiThreadedClientUse(t *testing.T) {
 	wg2.Wait()
 }
 
-func TestMultiThreadedClientSession(t *testing.T) {
-	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	addr := os.Getenv("TEST_KDC_ADDR")
-	if addr == "" {
-		addr = testdata.TEST_KDC_ADDR
-	}
-	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
-	err := cl.Login()
-	if err != nil {
-		t.Fatalf("failed to log in: %v", err)
-	}
-
-	s, ok := cl.sessions.get("TEST.GOKRB5")
-	if !ok {
-		t.Fatal("error initially getting session")
-	}
-	go func() {
-		for {
-			err := cl.renewTGT(s)
-			if err != nil {
-				t.Logf("error renewing TGT: %v", err)
-			}
-			time.Sleep(time.Millisecond * 100)
-		}
-	}()
-
-	var wg sync.WaitGroup
-	wg.Add(10)
-	for i := 0; i < 10; i++ {
-		go func() {
-			defer wg.Done()
-			tgt, _, err := cl.sessionTGT("TEST.GOKRB5")
-			if err != nil || tgt.Realm != "TEST.GOKRB5" {
-				t.Logf("error getting session: %v", err)
-			}
-			_, _, _, r, _ := cl.sessionTimes("TEST.GOKRB5")
-			fmt.Fprintf(ioutil.Discard, "%v", r)
-		}()
-		time.Sleep(time.Second)
-	}
-	wg.Wait()
-}
-
-func spnegoGet(cl *Client) error {
+func spnegoGet(cl *client.Client) error {
 	url := os.Getenv("TEST_HTTP_URL")
 	if url == "" {
 		url = testdata.TEST_HTTP_URL
@@ -460,7 +393,7 @@ func spnegoGet(cl *Client) error {
 	if httpResp.StatusCode != http.StatusUnauthorized {
 		return errors.New("did not get unauthorized code when no SPNEGO header set")
 	}
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		return fmt.Errorf("error setting client SPNEGO header: %v", err)
 	}
@@ -475,25 +408,27 @@ func spnegoGet(cl *Client) error {
 }
 
 func TestNewClientFromCCache(t *testing.T) {
+	test.Integration(t)
+
 	b, err := hex.DecodeString(testdata.CCACHE_TEST)
 	if err != nil {
 		t.Fatalf("error decoding test data")
 	}
-	cc, err := credentials.ParseCCache(b)
+	cc := new(credentials.CCache)
+	err = cc.Unmarshal(b)
 	if err != nil {
 		t.Fatal("error getting test CCache")
 	}
-	cl, err := NewClientFromCCache(cc)
-	if err != nil {
-		t.Fatalf("error creating client from CCache: %v", err)
-	}
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl.WithConfig(c)
+	cl, err := client.NewClientFromCCache(cc, c)
+	if err != nil {
+		t.Fatalf("error creating client from CCache: %v", err)
+	}
 	if ok, err := cl.IsConfigured(); !ok {
 		t.Fatalf("client was not configured from CCache: %v", err)
 	}
@@ -502,8 +437,11 @@ func TestNewClientFromCCache(t *testing.T) {
 // Login to the TEST.GOKRB5 domain and request service ticket for resource in the RESDOM.GOKRB5 domain.
 // There is a trust between the two domains.
 func TestClient_GetServiceTicket_Trusted_Resource_Domain(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 
 	addr := os.Getenv("TEST_KDC_ADDR")
@@ -520,12 +458,11 @@ func TestClient_GetServiceTicket_Trusted_Resource_Domain(t *testing.T) {
 	}
 
 	c.LibDefaults.DefaultRealm = "TEST.GOKRB5"
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 	c.LibDefaults.DefaultTktEnctypes = []string{"aes256-cts-hmac-sha1-96"}
 	c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]}
 	c.LibDefaults.DefaultTGSEnctypes = []string{"aes256-cts-hmac-sha1-96"}
 	c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["aes256-cts-hmac-sha1-96"]}
-	cl.WithConfig(c)
 
 	err := cl.Login()
 
@@ -537,12 +474,13 @@ func TestClient_GetServiceTicket_Trusted_Resource_Domain(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error getting service ticket: %v\n", err)
 	}
-	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
 	assert.Equal(t, etypeID.ETypesByName["aes256-cts-hmac-sha1-96"], key.KeyType)
 
 	b, _ = hex.DecodeString(testdata.SYSHTTP_RESDOM_KEYTAB)
-	skt, _ := keytab.Parse(b)
-	err = tkt.DecryptEncPart(skt, "")
+	skt := keytab.New()
+	skt.Unmarshal(b)
+	err = tkt.DecryptEncPart(skt, nil)
 	if err != nil {
 		t.Errorf("error decrypting ticket with service keytab: %v", err)
 	}
@@ -604,13 +542,15 @@ func getServiceTkt() error {
 	return nil
 }
 
-func loadCCache() (credentials.CCache, error) {
+func loadCCache() (*credentials.CCache, error) {
 	usr, _ := user.Current()
 	cpath := "/tmp/krb5cc_" + usr.Uid
 	return credentials.LoadCCache(cpath)
 }
 
 func TestGetServiceTicketFromCCacheTGT(t *testing.T) {
+	test.Privileged(t)
+
 	err := login()
 	if err != nil {
 		t.Fatalf("error logging in with kinit: %v", err)
@@ -625,17 +565,32 @@ func TestGetServiceTicketFromCCacheTGT(t *testing.T) {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	cfg.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
-	cl, err := NewClientFromCCache(c)
+	cl, err := client.NewClientFromCCache(c, cfg)
 	if err != nil {
 		t.Fatalf("error generating client from ccache: %v", err)
 	}
-	cl.WithConfig(cfg)
+	spn := "HTTP/host.test.gokrb5"
+	tkt, key, err := cl.GetServiceTicket(spn)
+	if err != nil {
+		t.Fatalf("error getting service ticket: %v\n", err)
+	}
+	assert.Equal(t, spn, tkt.SName.PrincipalNameString())
+	assert.Equal(t, int32(18), key.KeyType)
+
+	//Check cache use - should get the same values back again
+	tkt2, key2, err := cl.GetServiceTicket(spn)
+	if err != nil {
+		t.Fatalf("error getting service ticket: %v\n", err)
+	}
+	assert.Equal(t, tkt.EncPart.Cipher, tkt2.EncPart.Cipher)
+	assert.Equal(t, key.KeyValue, key2.KeyValue)
+
 	url := os.Getenv("TEST_HTTP_URL")
 	if url == "" {
 		url = testdata.TEST_HTTP_URL
 	}
 	r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		t.Fatalf("error setting client SPNEGO header: %v", err)
 	}
@@ -647,6 +602,8 @@ func TestGetServiceTicketFromCCacheTGT(t *testing.T) {
 }
 
 func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) {
+	test.Privileged(t)
+
 	err := login()
 	if err != nil {
 		t.Fatalf("error logging in with kinit: %v", err)
@@ -660,17 +617,16 @@ func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) {
 		t.Errorf("error loading CCache: %v", err)
 	}
 	cfg, _ := config.NewConfigFromString("...")
-	cl, err := NewClientFromCCache(c)
+	cl, err := client.NewClientFromCCache(c, cfg)
 	if err != nil {
 		t.Fatalf("error generating client from ccache: %v", err)
 	}
-	cl.WithConfig(cfg)
 	url := os.Getenv("TEST_HTTP_URL")
 	if url == "" {
 		url = testdata.TEST_HTTP_URL
 	}
 	r, _ := http.NewRequest("GET", url+"/modgssapi/index.html", nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		t.Fatalf("error setting client SPNEGO header: %v", err)
 	}
@@ -682,8 +638,11 @@ func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) {
 }
 
 func TestClient_ChangePasswd(t *testing.T) {
+	test.Integration(t)
+
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
@@ -691,8 +650,7 @@ func TestClient_ChangePasswd(t *testing.T) {
 	}
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
 	c.Realms[0].KPasswdServer = []string{addr + ":464"}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	ok, err := cl.ChangePasswd("newpassword")
 	if err != nil {
@@ -700,8 +658,7 @@ func TestClient_ChangePasswd(t *testing.T) {
 	}
 	assert.True(t, ok, "password was not changed")
 
-	cl = NewClientWithPassword("testuser1", "TEST.GOKRB5", "newpassword")
-	cl.WithConfig(c)
+	cl = client.NewClientWithPassword("testuser1", "TEST.GOKRB5", "newpassword", c)
 	ok, err = cl.ChangePasswd(testdata.TESTUSER1_PASSWORD)
 	if err != nil {
 		t.Fatalf("error changing password: %v", err)
@@ -709,61 +666,19 @@ func TestClient_ChangePasswd(t *testing.T) {
 	assert.True(t, ok, "password was not changed back")
 }
 
-func TestClient_AutoRenew_Goroutine(t *testing.T) {
-	// Tests that the auto renew of client credentials is not spawning goroutines out of control.
-	addr := os.Getenv("TEST_KDC_ADDR")
-	if addr == "" {
-		addr = testdata.TEST_KDC_ADDR
-	}
-	b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS}
-	c.LibDefaults.PreferredPreauthTypes = []int{int(etypeID.DES3_CBC_SHA1_KD)} // a preauth etype the KDC does not support. Test this does not cause renewal to fail.
-	cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
-
-	err := cl.Login()
-	if err != nil {
-		t.Errorf("error on logging in: %v\n", err)
-	}
-	n := runtime.NumGoroutine()
-	for i := 0; i < 24; i++ {
-		time.Sleep(time.Second * 5)
-		_, endTime, _, _, err := cl.sessionTimes("TEST.GOKRB5")
-		if err != nil {
-			t.Errorf("could not get client's session: %v", err)
-		}
-		if time.Now().UTC().After(endTime) {
-			t.Fatalf("session auto update failed")
-		}
-		spn := "HTTP/host.test.gokrb5"
-		tkt, key, err := cl.GetServiceTicket(spn)
-		if err != nil {
-			t.Fatalf("error getting service ticket: %v\n", err)
-		}
-		b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-		skt, _ := keytab.Parse(b)
-		tkt.DecryptEncPart(skt, "")
-		assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
-		assert.Equal(t, int32(18), key.KeyType)
-		if runtime.NumGoroutine() > n {
-			t.Fatalf("number of goroutines is increasing: should not be more than %d, is %d", n, runtime.NumGoroutine())
-		}
-	}
-}
-
 func TestClient_Destroy(t *testing.T) {
+	test.Integration(t)
+
 	addr := os.Getenv("TEST_KDC_ADDR")
 	if addr == "" {
 		addr = testdata.TEST_KDC_ADDR
 	}
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS}
-	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 
 	err := cl.Login()
 	if err != nil {

+ 0 - 46
client/http.go

@@ -1,46 +0,0 @@
-package client
-
-import (
-	"encoding/base64"
-	"fmt"
-	"net/http"
-	"strings"
-
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/gssapi"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
-)
-
-// SetSPNEGOHeader gets the service ticket and sets it 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(r *http.Request, spn string) error {
-	if spn == "" {
-		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)
-	}
-	err = SetSPNEGOHeader(*cl.Credentials, tkt, skey, r)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// SetSPNEGOHeader sets the provided ticket as the SPNEGO authorization header on HTTP request object.
-func SetSPNEGOHeader(creds credentials.Credentials, tkt messages.Ticket, sessionKey types.EncryptionKey, r *http.Request) error {
-	SPNEGOToken, err := gssapi.GetSPNEGOKrbNegTokenInit(creds, tkt, sessionKey)
-	if err != nil {
-		return krberror.Errorf(err, krberror.EncodingError, "could not generate SPNEGO negotiation token")
-	}
-	nb, err := SPNEGOToken.Marshal()
-	if err != nil {
-		return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO")
-	}
-	hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb)
-	r.Header.Set("Authorization", hs)
-	return nil
-}

+ 22 - 9
client/network.go

@@ -9,8 +9,8 @@ import (
 	"net"
 	"time"
 
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
 )
 
 // SendToKDC performs network actions to send data to the KDC.
@@ -68,6 +68,7 @@ func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) {
 	return rb, nil
 }
 
+// dialKDCTCP establishes a UDP connection to a KDC.
 func dialKDCUDP(count int, kdcs map[int]string) (conn *net.UDPConn, err error) {
 	i := 1
 	for i <= count {
@@ -78,7 +79,10 @@ func dialKDCUDP(count int, kdcs map[int]string) (conn *net.UDPConn, err error) {
 		}
 		conn, err = net.DialUDP("udp", nil, udpAddr)
 		if err == nil {
-			conn.SetDeadline(time.Now().Add(5 * time.Second))
+			err = conn.SetDeadline(time.Now().Add(5 * time.Second))
+			if err != nil {
+				return
+			}
 			return
 		}
 		i++
@@ -87,6 +91,7 @@ func dialKDCUDP(count int, kdcs map[int]string) (conn *net.UDPConn, err error) {
 	return
 }
 
+// dialKDCTCP establishes a TCP connection to a KDC.
 func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
 	i := 1
 	for i <= count {
@@ -97,7 +102,10 @@ func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
 		}
 		conn, err = net.DialTCP("tcp", nil, tcpAddr)
 		if err == nil {
-			conn.SetDeadline(time.Now().Add(5 * time.Second))
+			err = conn.SetDeadline(time.Now().Add(5 * time.Second))
+			if err != nil {
+				return
+			}
 			return
 		}
 		i++
@@ -106,7 +114,7 @@ func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
 	return
 }
 
-// Send the bytes to the KDC over UDP.
+// sendKDCUDP sends bytes to the KDC via UDP.
 func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
 	var r []byte
 	count, kdcs, err := cl.Config.GetKDCs(realm, false)
@@ -124,6 +132,7 @@ func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
 	return checkForKRBError(r)
 }
 
+// sendKDCTCP sends bytes to the KDC via TCP.
 func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
 	var r []byte
 	count, kdcs, err := cl.Config.GetKDCs(realm, true)
@@ -141,7 +150,7 @@ func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
 	return checkForKRBError(rb)
 }
 
-// Send the bytes over UDP.
+// sendUDP sends bytes to connection over UDP.
 func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
 	var r []byte
 	defer conn.Close()
@@ -161,7 +170,7 @@ func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
 	return r, nil
 }
 
-// Send the bytes over TCP.
+// sendTCP sends bytes to connection over TCP.
 func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
 	defer conn.Close()
 	var r []byte
@@ -178,10 +187,13 @@ func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
 		NB: network byte order == big endian
 	*/
 	var buf bytes.Buffer
-	binary.Write(&buf, binary.BigEndian, uint32(len(b)))
+	err := binary.Write(&buf, binary.BigEndian, uint32(len(b)))
+	if err != nil {
+		return r, err
+	}
 	b = append(buf.Bytes(), b...)
 
-	_, err := conn.Write(b)
+	_, err = conn.Write(b)
 	if err != nil {
 		return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
 	}
@@ -204,6 +216,7 @@ func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
 	return rb, nil
 }
 
+// checkForKRBError checks if the response bytes from the KDC are a KRBError.
 func checkForKRBError(b []byte) ([]byte, error) {
 	var KRBErr messages.KRBError
 	if err := KRBErr.Unmarshal(b); err == nil {

+ 6 - 6
client/passwd.go

@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"net"
 
-	"gopkg.in/jcmturner/gokrb5.v6/kadmin"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/kadmin"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
 )
 
 // Kpasswd server response codes.
@@ -22,16 +22,16 @@ const (
 
 // ChangePasswd changes the password of the client to the value provided.
 func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
-	ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Realm, cl.Config, cl.Credentials.CName)
+	ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
 	if err != nil {
 		return false, err
 	}
-	ASRep, err := cl.ASExchange(cl.Credentials.Realm, ASReq, 0)
+	ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
 	if err != nil {
 		return false, err
 	}
 
-	msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName, cl.Credentials.Realm, newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
+	msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
 	if err != nil {
 		return false, err
 	}
@@ -50,7 +50,7 @@ func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
 }
 
 func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
-	_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Realm, true)
+	_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true)
 	if err != nil {
 		return
 	}

+ 6 - 6
client/session.go

@@ -6,10 +6,10 @@ import (
 	"sync"
 	"time"
 
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // sessions hold TGTs and are keyed on the realm name
@@ -71,7 +71,7 @@ type session struct {
 
 // AddSession adds a session for a realm with a TGT to the client's session cache.
 // A goroutine is started to automatically renew the TGT before expiry.
-func (cl *Client) AddSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
+func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
 	if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" {
 		// Not a TGT
 		return
@@ -176,7 +176,7 @@ func (cl *Client) renewTGT(s *session) error {
 		NameType:   nametype.KRB_NT_SRV_INST,
 		NameString: []string{"krbtgt", realm},
 	}
-	_, tgsRep, err := cl.TGSExchange(spn, cl.Credentials.Realm, tgt, skey, true, 0)
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true)
 	if err != nil {
 		return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT")
 	}

+ 115 - 0
client/session_test.go

@@ -0,0 +1,115 @@
+package client
+
+import (
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"runtime"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+)
+
+func TestMultiThreadedClientSession(t *testing.T) {
+	test.Integration(t)
+
+	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	addr := os.Getenv("TEST_KDC_ADDR")
+	if addr == "" {
+		addr = testdata.TEST_KDC_ADDR
+	}
+	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
+	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
+	err := cl.Login()
+	if err != nil {
+		t.Fatalf("failed to log in: %v", err)
+	}
+
+	s, ok := cl.sessions.get("TEST.GOKRB5")
+	if !ok {
+		t.Fatal("error initially getting session")
+	}
+	go func() {
+		for {
+			err := cl.renewTGT(s)
+			if err != nil {
+				t.Logf("error renewing TGT: %v", err)
+			}
+			time.Sleep(time.Millisecond * 100)
+		}
+	}()
+
+	var wg sync.WaitGroup
+	wg.Add(10)
+	for i := 0; i < 10; i++ {
+		go func() {
+			defer wg.Done()
+			tgt, _, err := cl.sessionTGT("TEST.GOKRB5")
+			if err != nil || tgt.Realm != "TEST.GOKRB5" {
+				t.Logf("error getting session: %v", err)
+			}
+			_, _, _, r, _ := cl.sessionTimes("TEST.GOKRB5")
+			fmt.Fprintf(ioutil.Discard, "%v", r)
+		}()
+		time.Sleep(time.Second)
+	}
+	wg.Wait()
+}
+
+func TestClient_AutoRenew_Goroutine(t *testing.T) {
+	test.Integration(t)
+
+	// Tests that the auto renew of client credentials is not spawning goroutines out of control.
+	addr := os.Getenv("TEST_KDC_ADDR")
+	if addr == "" {
+		addr = testdata.TEST_KDC_ADDR
+	}
+	b, _ := hex.DecodeString(testdata.TESTUSER2_KEYTAB)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC_SHORTTICKETS}
+	c.LibDefaults.PreferredPreauthTypes = []int{int(etypeID.DES3_CBC_SHA1_KD)} // a preauth etype the KDC does not support. Test this does not cause renewal to fail.
+	cl := NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c)
+
+	err := cl.Login()
+	if err != nil {
+		t.Errorf("error on logging in: %v\n", err)
+	}
+	n := runtime.NumGoroutine()
+	for i := 0; i < 24; i++ {
+		time.Sleep(time.Second * 5)
+		_, endTime, _, _, err := cl.sessionTimes("TEST.GOKRB5")
+		if err != nil {
+			t.Errorf("could not get client's session: %v", err)
+		}
+		if time.Now().UTC().After(endTime) {
+			t.Fatalf("session auto update failed")
+		}
+		spn := "HTTP/host.test.gokrb5"
+		tkt, key, err := cl.GetServiceTicket(spn)
+		if err != nil {
+			t.Fatalf("error getting service ticket: %v\n", err)
+		}
+		b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+		skt := keytab.New()
+		skt.Unmarshal(b)
+		tkt.DecryptEncPart(skt, nil)
+		assert.Equal(t, spn, tkt.SName.PrincipalNameString())
+		assert.Equal(t, int32(18), key.KeyType)
+		if runtime.NumGoroutine() > n {
+			t.Fatalf("number of goroutines is increasing: should not be more than %d, is %d", n, runtime.NumGoroutine())
+		}
+	}
+}

+ 69 - 0
client/settings.go

@@ -0,0 +1,69 @@
+package client
+
+import "log"
+
+// Settings holds optional client settings.
+type Settings struct {
+	disablePAFXFast         bool
+	assumePreAuthentication bool
+	preAuthEType            int32
+	logger                  *log.Logger
+}
+
+// NewSettings creates a new client settings struct.
+func NewSettings(settings ...func(*Settings)) *Settings {
+	s := new(Settings)
+	for _, set := range settings {
+		set(s)
+	}
+	return s
+}
+
+// DisablePAFXFAST used to configure the client to not use PA_FX_FAST.
+//
+// s := NewSettings(DisablePAFXFAST(true))
+func DisablePAFXFAST(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.disablePAFXFast = b
+	}
+}
+
+// DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST.
+func (s *Settings) DisablePAFXFAST() bool {
+	return s.disablePAFXFast
+}
+
+// AssumePreAuthentication used to configure the client to assume pre-authentication is required.
+//
+// s := NewSettings(AssumePreAuthentication(true))
+func AssumePreAuthentication(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.disablePAFXFast = b
+	}
+}
+
+// AssumePreAuthentication indicates if the client should proactively assume using pre-authentication.
+func (s *Settings) AssumePreAuthentication() bool {
+	return s.assumePreAuthentication
+}
+
+// Logger used to configure client with a logger.
+//
+// s := NewSettings(kt, Logger(l))
+func Logger(l *log.Logger) func(*Settings) {
+	return func(s *Settings) {
+		s.logger = l
+	}
+}
+
+// Logger returns the client logger instance.
+func (s *Settings) Logger() *log.Logger {
+	return s.logger
+}
+
+// Log will write to the service's logger if it is configured.
+func (cl *Client) Log(format string, v ...interface{}) {
+	if cl.settings.Logger() != nil {
+		cl.settings.Logger().Printf(format, v...)
+	}
+}

+ 1 - 1
config/krb5conf.go

@@ -16,7 +16,7 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // Config represents the KRB5 configuration.

+ 27 - 31
credentials/ccache.go

@@ -10,7 +10,7 @@ import (
 	"unsafe"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -31,7 +31,7 @@ type CCache struct {
 	Version          uint8
 	Header           header
 	DefaultPrincipal principal
-	Credentials      []credential
+	Credentials      []*Credential
 	Path             string
 }
 
@@ -52,7 +52,7 @@ type principal struct {
 	PrincipalName types.PrincipalName
 }
 
-type credential struct {
+type Credential struct {
 	Client       principal
 	Server       principal
 	Key          types.EncryptionKey
@@ -69,33 +69,29 @@ type credential struct {
 }
 
 // LoadCCache loads a credential cache file into a CCache type.
-func LoadCCache(cpath string) (CCache, error) {
-	k, err := ioutil.ReadFile(cpath)
+func LoadCCache(cpath string) (*CCache, error) {
+	c := new(CCache)
+	b, err := ioutil.ReadFile(cpath)
 	if err != nil {
-		return CCache{}, err
+		return c, err
 	}
-	c, err := ParseCCache(k)
-	c.Path = cpath
+	err = c.Unmarshal(b)
 	return c, err
 }
 
 // ParseCCache byte slice of credential cache data into CCache type.
-func ParseCCache(b []byte) (c CCache, err error) {
+func (c *CCache) Unmarshal(b []byte) error {
 	p := 0
 	//The first byte of the file always has the value 5
 	if int8(b[p]) != 5 {
-		err = errors.New("Invalid credential cache data. First byte does not equal 5")
-		return
+		return errors.New("Invalid credential cache data. First byte does not equal 5")
 	}
 	p++
 	//Get credential cache version
 	//The second byte contains the version number (1 to 4)
 	c.Version = b[p]
 	if c.Version < 1 || c.Version > 4 {
-		err = errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
-		if err != nil {
-			return
-		}
+		return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
 	}
 	p++
 	//Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
@@ -105,21 +101,20 @@ func ParseCCache(b []byte) (c CCache, err error) {
 		endian = binary.LittleEndian
 	}
 	if c.Version == 4 {
-		err = parseHeader(b, &p, &c, &endian)
+		err := parseHeader(b, &p, c, &endian)
 		if err != nil {
-			return
+			return err
 		}
 	}
-	c.DefaultPrincipal = parsePrincipal(b, &p, &c, &endian)
+	c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian)
 	for p < len(b) {
-		cred, e := parseCredential(b, &p, &c, &endian)
-		if e != nil {
-			err = e
-			return
+		cred, err := parseCredential(b, &p, c, &endian)
+		if err != nil {
+			return err
 		}
 		c.Credentials = append(c.Credentials, cred)
 	}
-	return
+	return nil
 }
 
 func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error {
@@ -163,7 +158,8 @@ func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ pri
 	return princ
 }
 
-func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred credential, err error) {
+func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) {
+	cred = new(Credential)
 	cred.Client = parsePrincipal(b, p, c, e)
 	cred.Server = parsePrincipal(b, p, c, e)
 	key := types.EncryptionKey{}
@@ -213,9 +209,9 @@ func (c *CCache) GetClientRealm() string {
 // GetClientCredentials returns a Credentials object representing the client of the credentials cache.
 func (c *CCache) GetClientCredentials() *Credentials {
 	return &Credentials{
-		Username: c.DefaultPrincipal.PrincipalName.GetPrincipalNameString(),
-		Realm:    c.GetClientRealm(),
-		CName:    c.DefaultPrincipal.PrincipalName,
+		username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(),
+		realm:    c.GetClientRealm(),
+		cname:    c.DefaultPrincipal.PrincipalName,
 	}
 }
 
@@ -230,8 +226,8 @@ func (c *CCache) Contains(p types.PrincipalName) bool {
 }
 
 // GetEntry returns a specific credential for the PrincipalName provided.
-func (c *CCache) GetEntry(p types.PrincipalName) (credential, bool) {
-	var cred credential
+func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) {
+	cred := new(Credential)
 	var found bool
 	for i := range c.Credentials {
 		if c.Credentials[i].Server.PrincipalName.Equal(p) {
@@ -247,8 +243,8 @@ func (c *CCache) GetEntry(p types.PrincipalName) (credential, bool) {
 }
 
 // GetEntries filters out configuration entries an returns a slice of credentials.
-func (c *CCache) GetEntries() []credential {
-	var creds []credential
+func (c *CCache) GetEntries() []*Credential {
+	creds := make([]*Credential, 0)
 	for _, cred := range c.Credentials {
 		// Filter out configuration entries
 		if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") {

+ 10 - 8
credentials/ccache_integration_test.go

@@ -1,6 +1,3 @@
-// +build integration
-// To turn on this test use -tags=integration in go test command
-
 package credentials
 
 import (
@@ -15,9 +12,10 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -126,13 +124,15 @@ func klist() ([]string, error) {
 	return stdout.Lines(), nil
 }
 
-func loadCCache() (CCache, error) {
+func loadCCache() (*CCache, error) {
 	usr, _ := user.Current()
 	cpath := "/tmp/krb5cc_" + usr.Uid
 	return LoadCCache(cpath)
 }
 
 func TestLoadCCache(t *testing.T) {
+	test.Privileged(t)
+
 	err := login()
 	if err != nil {
 		t.Fatalf("error logging in with kinit: %v", err)
@@ -142,11 +142,13 @@ func TestLoadCCache(t *testing.T) {
 		t.Errorf("error loading CCache: %v", err)
 	}
 	pn := c.GetClientPrincipalName()
-	assert.Equal(t, "testuser1", pn.GetPrincipalNameString(), "principal not as expected")
+	assert.Equal(t, "testuser1", pn.PrincipalNameString(), "principal not as expected")
 	assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "realm not as expected")
 }
 
 func TestCCacheEntries(t *testing.T) {
+	test.Privileged(t)
+
 	err := login()
 	if err != nil {
 		t.Fatalf("error logging in with kinit: %v", err)

+ 19 - 13
credentials/ccache_test.go

@@ -5,9 +5,9 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 func TestParse(t *testing.T) {
@@ -16,7 +16,8 @@ func TestParse(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}
@@ -25,7 +26,7 @@ func TestParse(t *testing.T) {
 	assert.Equal(t, uint16(1), c.Header.fields[0].tag, "Header tag not as expected")
 	assert.Equal(t, uint16(8), c.Header.fields[0].length, "Length of header not as expected")
 	assert.Equal(t, "TEST.GOKRB5", c.DefaultPrincipal.Realm, "Default client principal realm not as expected")
-	assert.Equal(t, "testuser1", c.DefaultPrincipal.PrincipalName.GetPrincipalNameString(), "Default client principaal name not as expected")
+	assert.Equal(t, "testuser1", c.DefaultPrincipal.PrincipalName.PrincipalNameString(), "Default client principaal name not as expected")
 	assert.Equal(t, 3, len(c.Credentials), "Number of credentials not as expected")
 	tgtpn := types.PrincipalName{
 		NameType:   nametype.KRB_NT_SRV_INST,
@@ -45,7 +46,8 @@ func TestCCache_GetClientPrincipalName(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}
@@ -62,7 +64,8 @@ func TestCCache_GetClientCredentials(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}
@@ -71,9 +74,9 @@ func TestCCache_GetClientCredentials(t *testing.T) {
 		NameString: []string{"testuser1"},
 	}
 	cred := c.GetClientCredentials()
-	assert.Equal(t, "TEST.GOKRB5", cred.Realm, "Client realm in credential not as expected")
-	assert.Equal(t, pn, cred.CName, "Client Principal Name not as expected")
-	assert.Equal(t, "testuser1", cred.Username, "Username not as expected")
+	assert.Equal(t, "TEST.GOKRB5", cred.Domain(), "Client realm in credential not as expected")
+	assert.Equal(t, pn, cred.CName(), "Client Principal Name not as expected")
+	assert.Equal(t, "testuser1", cred.UserName(), "Username not as expected")
 }
 
 func TestCCache_GetClientRealm(t *testing.T) {
@@ -82,7 +85,8 @@ func TestCCache_GetClientRealm(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}
@@ -95,7 +99,8 @@ func TestCCache_GetEntry(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}
@@ -116,7 +121,8 @@ func TestCCache_GetEntries(t *testing.T) {
 	if err != nil {
 		t.Fatal("Error decoding test data")
 	}
-	c, err := ParseCCache(b)
+	c := new(CCache)
+	err = c.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing cache: %v", err)
 	}

+ 75 - 42
credentials/credentials.go

@@ -5,9 +5,9 @@ import (
 	"time"
 
 	"github.com/hashicorp/go-uuid"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -19,14 +19,14 @@ const (
 // Contains either a keytab, password or both.
 // Keytabs are used over passwords if both are defined.
 type Credentials struct {
-	Username    string
+	username    string
 	displayName string
-	Realm       string
-	CName       types.PrincipalName
-	Keytab      keytab.Keytab
-	Password    string
+	realm       string
+	cname       types.PrincipalName
+	keytab      *keytab.Keytab
+	password    string
 	attributes  map[string]interface{}
-	ValidUntil  time.Time
+	validUntil  time.Time
 
 	authenticated   bool
 	human           bool
@@ -51,73 +51,86 @@ type ADCredentials struct {
 }
 
 // NewCredentials creates a new Credentials instance.
-func NewCredentials(username string, realm string) Credentials {
+func New(username string, realm string) *Credentials {
 	uid, err := uuid.GenerateUUID()
 	if err != nil {
 		uid = "00unique-sess-ions-uuid-unavailable0"
 	}
-	return Credentials{
-		Username:    username,
-		displayName: username,
-		Realm:       realm,
-		CName:       types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username),
-		Keytab:      keytab.NewKeytab(),
-		attributes:  make(map[string]interface{}),
-		sessionID:   uid,
+	return &Credentials{
+		username:        username,
+		displayName:     username,
+		realm:           realm,
+		cname:           types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username),
+		keytab:          keytab.New(),
+		attributes:      make(map[string]interface{}),
+		groupMembership: make(map[string]bool),
+		sessionID:       uid,
+		human:           true,
 	}
 }
 
-// NewCredentialsFromPrincipal creates a new Credentials instance with the user details provides as a PrincipalName type.
-func NewCredentialsFromPrincipal(cname types.PrincipalName, realm string) Credentials {
+// NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type.
+func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials {
 	uid, err := uuid.GenerateUUID()
 	if err != nil {
 		uid = "00unique-sess-ions-uuid-unavailable0"
 	}
-	return Credentials{
-		Username:        cname.GetPrincipalNameString(),
-		displayName:     cname.GetPrincipalNameString(),
-		Realm:           realm,
-		CName:           cname,
-		Keytab:          keytab.NewKeytab(),
+	return &Credentials{
+		username:        cname.PrincipalNameString(),
+		displayName:     cname.PrincipalNameString(),
+		realm:           realm,
+		cname:           cname,
+		keytab:          keytab.New(),
 		attributes:      make(map[string]interface{}),
 		groupMembership: make(map[string]bool),
 		sessionID:       uid,
+		human:           true,
 	}
 }
 
 // WithKeytab sets the Keytab in the Credentials struct.
-func (c *Credentials) WithKeytab(kt keytab.Keytab) *Credentials {
-	c.Keytab = kt
+func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials {
+	c.keytab = kt
 	return c
 }
 
-// WithPassword sets the password in the Credentials struct.
-func (c *Credentials) WithPassword(password string) *Credentials {
-	c.Password = password
-	return c
+// Keytab returns the credential's Keytab.
+func (c *Credentials) Keytab() *keytab.Keytab {
+	return c.keytab
 }
 
 // HasKeytab queries if the Credentials has a keytab defined.
 func (c *Credentials) HasKeytab() bool {
-	if len(c.Keytab.Entries) > 0 {
+	if c.keytab != nil && len(c.keytab.Entries) > 0 {
 		return true
 	}
 	return false
 }
 
-// SetValidUntil sets the expiry time of the credentials
-func (c *Credentials) SetValidUntil(t time.Time) {
-	c.ValidUntil = t
+// WithPassword sets the password in the Credentials struct.
+func (c *Credentials) WithPassword(password string) *Credentials {
+	c.password = password
+	return c
+}
+
+// Password returns the credential's password.
+func (c *Credentials) Password() string {
+	return c.password
 }
 
 // HasPassword queries if the Credentials has a password defined.
 func (c *Credentials) HasPassword() bool {
-	if c.Password != "" {
+	if c.password != "" {
 		return true
 	}
 	return false
 }
 
+// SetValidUntil sets the expiry time of the credentials
+func (c *Credentials) SetValidUntil(t time.Time) {
+	c.validUntil = t
+}
+
 // SetADCredentials adds ADCredentials attributes to the credentials
 func (c *Credentials) SetADCredentials(a ADCredentials) {
 	c.SetAttribute(AttributeKeyADCredentials, a)
@@ -136,22 +149,42 @@ func (c *Credentials) SetADCredentials(a ADCredentials) {
 
 // UserName returns the credential's username.
 func (c *Credentials) UserName() string {
-	return c.Username
+	return c.username
 }
 
 // SetUserName sets the username value on the credential.
 func (c *Credentials) SetUserName(s string) {
-	c.Username = s
+	c.username = s
+}
+
+// CName returns the credential's client principal name.
+func (c *Credentials) CName() types.PrincipalName {
+	return c.cname
+}
+
+// SetCName sets the client principal name on the credential.
+func (c *Credentials) SetCName(pn types.PrincipalName) {
+	c.cname = pn
 }
 
 // Domain returns the credential's domain.
 func (c *Credentials) Domain() string {
-	return c.Realm
+	return c.realm
 }
 
 // SetDomain sets the domain value on the credential.
 func (c *Credentials) SetDomain(s string) {
-	c.Realm = s
+	c.realm = s
+}
+
+// Realm returns the credential's realm. Same as the domain.
+func (c *Credentials) Realm() string {
+	return c.Domain()
+}
+
+// SetRealm sets the realm value on the credential. Same as the domain
+func (c *Credentials) SetRealm(s string) {
+	c.SetDomain(s)
 }
 
 // DisplayName returns the credential's display name.
@@ -247,7 +280,7 @@ func (c *Credentials) SessionID() string {
 
 // Expired indicates if the credential has expired.
 func (c *Credentials) Expired() bool {
-	if !c.ValidUntil.IsZero() && time.Now().UTC().After(c.ValidUntil) {
+	if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) {
 		return true
 	}
 	return false

+ 5 - 5
crypto/aes128-cts-hmac-sha1-96.go

@@ -6,11 +6,11 @@ import (
 	"crypto/sha1"
 	"hash"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3961"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3962"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // RFC 3962

+ 2 - 2
crypto/aes128-cts-hmac-sha1-96_test.go

@@ -5,8 +5,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3962"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
 )
 
 func TestAes128CtsHmacSha196_StringToKey(t *testing.T) {

+ 4 - 4
crypto/aes128-cts-hmac-sha256-128.go

@@ -6,10 +6,10 @@ import (
 	"crypto/sha256"
 	"hash"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc8009"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // RFC https://tools.ietf.org/html/rfc8009

+ 2 - 2
crypto/aes128-cts-hmac-sha256-128_test.go

@@ -5,8 +5,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc8009"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
 )
 
 func TestAes128CtsHmacSha256128_StringToKey(t *testing.T) {

+ 5 - 5
crypto/aes256-cts-hmac-sha1-96.go

@@ -6,11 +6,11 @@ import (
 	"crypto/sha1"
 	"hash"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3961"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3962"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // RFC 3962

+ 2 - 2
crypto/aes256-cts-hmac-sha1-96_test.go

@@ -5,8 +5,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3962"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962"
 )
 
 func TestAes256CtsHmacSha196_StringToKey(t *testing.T) {

+ 4 - 4
crypto/aes256-cts-hmac-sha384-192.go

@@ -6,10 +6,10 @@ import (
 	"crypto/sha512"
 	"hash"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc8009"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // RFC https://tools.ietf.org/html/rfc8009

+ 2 - 2
crypto/aes256-cts-hmac-sha384-192_test.go

@@ -5,8 +5,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc8009"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009"
 )
 
 func TestAes256CtsHmacSha384192_StringToKey(t *testing.T) {

+ 1 - 1
crypto/common/common.go

@@ -9,7 +9,7 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 // ZeroPad pads bytes with zeros to nearest multiple of message size m.

+ 5 - 5
crypto/crypto.go

@@ -5,11 +5,11 @@ import (
 	"encoding/hex"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // GetEtype returns an instances of the required etype struct for the etype ID.

+ 4 - 4
crypto/des3-cbc-sha1-kd.go

@@ -7,10 +7,10 @@ import (
 	"errors"
 	"hash"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3961"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 //RFC: 3961 Section 6.3

+ 4 - 4
crypto/rc4-hmac.go

@@ -8,10 +8,10 @@ import (
 	"io"
 
 	"golang.org/x/crypto/md4"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc3961"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/rfc4757"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 //http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/security/krb5/internal/crypto/dk/ArcFourCrypto.java#ArcFourCrypto.encrypt%28byte%5B%5D%2Cint%2Cbyte%5B%5D%2Cbyte%5B%5D%2Cbyte%5B%5D%2Cint%2Cint%29

+ 2 - 2
crypto/rfc3961/encryption.go

@@ -9,8 +9,8 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 // DES3EncryptData encrypts the data provided using DES3 and methods specific to the etype provided.

+ 1 - 1
crypto/rfc3961/keyDerivation.go

@@ -3,7 +3,7 @@ package rfc3961
 import (
 	"bytes"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 const (

+ 2 - 2
crypto/rfc3962/encryption.go

@@ -7,8 +7,8 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/aescts.v1"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 3962.

+ 1 - 1
crypto/rfc3962/keyDerivation.go

@@ -6,7 +6,7 @@ import (
 	"errors"
 
 	"github.com/jcmturner/gofork/x/crypto/pbkdf2"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 const (

+ 1 - 1
crypto/rfc4757/encryption.go

@@ -8,7 +8,7 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
 )
 
 // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 4757.

+ 3 - 3
crypto/rfc8009/encryption.go

@@ -9,9 +9,9 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/aescts.v1"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/common"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/common"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 // EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 8009.

+ 2 - 2
crypto/rfc8009/keyDerivation.go

@@ -7,8 +7,8 @@ import (
 	"errors"
 
 	"golang.org/x/crypto/pbkdf2"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto/etype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
 )
 
 const (

+ 22 - 24
examples/example-AD.go

@@ -6,12 +6,13 @@ import (
 	"encoding/hex"
 	"fmt"
 	"gopkg.in/jcmturner/goidentity.v3"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/service"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/spnego"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 	"io/ioutil"
 	"log"
 	"net/http"
@@ -24,25 +25,23 @@ func main() {
 	defer s.Close()
 
 	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	cl := client.NewClientWithKeytab("testuser1", "USER.GOKRB5", kt)
-	cl.WithConfig(c)
-	cl.GoKrb5Conf.DisablePAFXFast = true
+	cl := client.NewClientWithKeytab("testuser1", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true))
 	httpRequest(s.URL, cl)
 
 	b, _ = hex.DecodeString(testdata.TESTUSER2_USERKRB5_AD_KEYTAB)
-	kt, _ = keytab.Parse(b)
+	kt = keytab.New()
+	kt.Unmarshal(b)
 	c, _ = config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	cl = client.NewClientWithKeytab("testuser2", "USER.GOKRB5", kt)
-	cl.WithConfig(c)
-	cl.GoKrb5Conf.DisablePAFXFast = true
+	cl = client.NewClientWithKeytab("testuser2", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true))
 	httpRequest(s.URL, cl)
 
 	//httpRequest("http://host.test.gokrb5/index.html")
 }
 
-func httpRequest(url string, cl client.Client) {
+func httpRequest(url string, cl *client.Client) {
 	l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile)
 
 	err := cl.Login()
@@ -50,7 +49,7 @@ func httpRequest(url string, cl client.Client) {
 		l.Printf("Error on AS_REQ: %v\n", err)
 	}
 	r, _ := http.NewRequest("GET", url, nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.res.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		l.Printf("Error setting client SPNEGO header: %v", err)
 	}
@@ -64,21 +63,20 @@ func httpRequest(url string, cl client.Client) {
 }
 
 func httpServer() *httptest.Server {
-	l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile)
-	b, _ := hex.DecodeString(testdata.SYSHTTP_RESGOKRB5_AD_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
+	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	th := http.HandlerFunc(testAppHandler)
-	c := service.NewConfig(kt)
-	c.ServicePrincipal = "sysHTTP"
-	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, c, l))
+	s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
 	return s
 }
 
 func testAppHandler(w http.ResponseWriter, r *http.Request) {
 	ctx := r.Context()
 	fmt.Fprint(w, "<html>\n<p><h1>TEST.GOKRB5 Handler</h1></p>\n")
-	if validuser, ok := ctx.Value(service.CTXKeyAuthenticated).(bool); ok && validuser {
-		if creds, ok := ctx.Value(service.CTXKeyCredentials).(goidentity.Identity); ok {
+	if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser {
+		if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok {
 			fmt.Fprintf(w, "<ul><li>Authenticed user: %s</li>\n", creds.UserName())
 			fmt.Fprintf(w, "<li>User's realm: %s</li>\n", creds.Domain())
 			fmt.Fprint(w, "<li>Authz Attributes (Group Memberships):</li><ul>\n")

+ 21 - 19
examples/example.go

@@ -1,5 +1,6 @@
 // +build examples
 
+// Package examples provides simple examples of gokrb5 use.
 package main
 
 import (
@@ -12,11 +13,12 @@ import (
 	"os"
 
 	"gopkg.in/jcmturner/goidentity.v3"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/service"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/spnego"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func main() {
@@ -24,23 +26,23 @@ func main() {
 	defer s.Close()
 
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.LibDefaults.NoAddresses = true
-	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 	httpRequest(s.URL, cl)
 
 	b, _ = hex.DecodeString(testdata.TESTUSER2_KEYTAB)
-	kt, _ = keytab.Parse(b)
+	kt = keytab.New()
+	kt.Unmarshal(b)
 	c, _ = config.NewConfigFromString(testdata.TEST_KRB5CONF)
 	c.LibDefaults.NoAddresses = true
-	cl = client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl = client.NewClientWithKeytab("testuser2", "TEST.GOKRB5", kt, c)
 	httpRequest(s.URL, cl)
 }
 
-func httpRequest(url string, cl client.Client) {
+func httpRequest(url string, cl *client.Client) {
 	l := log.New(os.Stderr, "GOKRB5 Client: ", log.Ldate|log.Ltime|log.Lshortfile)
 
 	err := cl.Login()
@@ -48,7 +50,7 @@ func httpRequest(url string, cl client.Client) {
 		l.Printf("Error on AS_REQ: %v\n", err)
 	}
 	r, _ := http.NewRequest("GET", url, nil)
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		l.Printf("Error setting client SPNEGO header: %v", err)
 	}
@@ -62,20 +64,20 @@ func httpRequest(url string, cl client.Client) {
 }
 
 func httpServer() *httptest.Server {
-	l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile)
+	l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	th := http.HandlerFunc(testAppHandler)
-	c := service.NewConfig(kt)
-	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, c, l))
+	s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
 	return s
 }
 
 func testAppHandler(w http.ResponseWriter, r *http.Request) {
 	ctx := r.Context()
 	fmt.Fprint(w, "<html>\n<p><h1>TEST.GOKRB5 Handler</h1></p>\n")
-	if validuser, ok := ctx.Value(service.CTXKeyAuthenticated).(bool); ok && validuser {
-		if creds, ok := ctx.Value(service.CTXKeyCredentials).(goidentity.Identity); ok {
+	if validuser, ok := ctx.Value(spnego.CTXKeyAuthenticated).(bool); ok && validuser {
+		if creds, ok := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity); ok {
 			fmt.Fprintf(w, "<ul><li>Authenticed user: %s</li>\n", creds.UserName())
 			fmt.Fprintf(w, "<li>User's realm: %s</li></ul>\n", creds.Domain())
 		}

+ 11 - 11
examples/httpClient.go

@@ -10,10 +10,11 @@ import (
 	"os"
 
 	//"github.com/pkg/profile"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/spnego"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 const (
@@ -44,14 +45,12 @@ func main() {
 	//defer profile.Start(profile.TraceProfile).Stop()
 	// Load the keytab
 	kb, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, err := keytab.Parse(kb)
+	kt := keytab.New()
+	err := kt.Unmarshal(kb)
 	if err != nil {
 		panic(err)
 	}
 
-	// Create the client with the keytab
-	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-
 	// Load the client krb5 config
 	conf, err := config.NewConfigFromString(kRB5CONF)
 	if err != nil {
@@ -61,8 +60,9 @@ func main() {
 	if addr != "" {
 		conf.Realms[0].KDC = []string{addr + ":88"}
 	}
-	// Apply the config to the client
-	cl.WithConfig(conf)
+
+	// Create the client with the keytab
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, conf)
 
 	// Log in the client
 	err = cl.Login()
@@ -77,7 +77,7 @@ func main() {
 		panic(err)
 	}
 	// Apply the client's auth headers to the request
-	err = cl.SetSPNEGOHeader(r, "HTTP/host.test.gokrb5")
+	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
 		panic(err)
 	}

+ 8 - 7
examples/httpServer.go

@@ -10,9 +10,10 @@ import (
 	"os"
 
 	goidentity "gopkg.in/jcmturner/goidentity.v3"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/service"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/spnego"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 const (
@@ -26,15 +27,15 @@ func main() {
 
 	// Load the service's keytab
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 
 	// Create the application's specific handler
 	th := http.HandlerFunc(testAppHandler)
 
 	// Set up handler mappings wrapping in the SPNEGOKRB5Authenticate handler wrapper
 	mux := http.NewServeMux()
-	c := service.NewConfig(kt)
-	mux.Handle("/", service.SPNEGOKRB5Authenticate(th, c, l))
+	mux.Handle("/", spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
 
 	// Start up the web server
 	log.Fatal(http.ListenAndServe(port, mux))
@@ -44,7 +45,7 @@ func main() {
 func testAppHandler(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	ctx := r.Context()
-	creds := ctx.Value(service.CTXKeyCredentials).(goidentity.Identity)
+	creds := ctx.Value(spnego.CTXKeyCredentials).(goidentity.Identity)
 	fmt.Fprintf(w,
 		`<html>
 <h1>GOKRB5 Handler</h1>

+ 0 - 36
gssapi/ContextFlags.go

@@ -1,36 +0,0 @@
-package gssapi
-
-import "github.com/jcmturner/gofork/encoding/asn1"
-
-/*
-ContextFlags ::= BIT STRING {
-  delegFlag       (0),
-  mutualFlag      (1),
-  replayFlag      (2),
-  sequenceFlag    (3),
-  anonFlag        (4),
-  confFlag        (5),
-  integFlag       (6)
-} (SIZE (32))
-*/
-
-const (
-	delegFlag    = 0
-	mutualFlag   = 1
-	replayFlag   = 2
-	sequenceFlag = 3
-	anonFlag     = 4
-	confFlag     = 5
-	integFlag    = 6
-)
-
-// ContextFlags flags for GSSAPI
-type ContextFlags asn1.BitString
-
-// NewContextFlags creates a new ContextFlags instance.
-func NewContextFlags() ContextFlags {
-	var c ContextFlags
-	c.BitLength = 32
-	c.Bytes = make([]byte, 4)
-	return c
-}

+ 8 - 8
gssapi/MICToken.go

@@ -8,9 +8,9 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 /*
@@ -41,11 +41,11 @@ From RFC 4121, section 4.2.6.1:
 */
 
 const (
-	// When set, this flag indicates the sender is the context acceptor.  When not set, it indicates the sender is the context initiator
+	// MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor.  When not set, it indicates the sender is the context initiator
 	MICTokenFlagSentByAcceptor = 1 << iota
-	// This flag indicates confidentiality is provided for.  It SHALL NOT be set in MIC tokens
+	// MICTokenFlagSentByAcceptor - this flag indicates confidentiality is provided for.  It SHALL NOT be set in MIC tokens
 	MICTokenFlagSealed
-	// A subkey asserted by the context acceptor is used to protect the message
+	// MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message
 	MICTokenFlagAcceptorSubkey
 )
 
@@ -135,10 +135,10 @@ func (mt *MICToken) getMICChecksumHeader() []byte {
 	return header
 }
 
-// VerifyChecksum computes the token's checksum with the provided key and usage,
+// Verify computes the token's checksum with the provided key and usage,
 // and compares it to the checksum present in the token.
 // In case of any failure, (false, err) is returned, with err an explanatory error.
-func (mt *MICToken) VerifyChecksum(key types.EncryptionKey, keyUsage uint32) (bool, error) {
+func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
 	computed, err := mt.checksum(key, keyUsage)
 	if err != nil {
 		return false, err

+ 6 - 6
gssapi/MICToken_test.go

@@ -6,8 +6,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -101,7 +101,7 @@ func TestMICChallengeChecksumVerification(t *testing.T) {
 	var mt MICToken
 	mt.Unmarshal(challenge, true)
 	mt.Payload, _ = hex.DecodeString(testMICPayload)
-	challengeOk, cErr := mt.VerifyChecksum(getSessionKey(), acceptorSign)
+	challengeOk, cErr := mt.Verify(getSessionKey(), acceptorSign)
 	assert.Nil(t, cErr, "Error occurred during checksum verification.")
 	assert.True(t, challengeOk, "Checksum verification failed.")
 }
@@ -112,7 +112,7 @@ func TestMICResponseChecksumVerification(t *testing.T) {
 	var mt MICToken
 	mt.Unmarshal(reply, false)
 	mt.Payload, _ = hex.DecodeString(testMICPayload)
-	replyOk, rErr := mt.VerifyChecksum(getSessionKey(), initiatorSign)
+	replyOk, rErr := mt.Verify(getSessionKey(), initiatorSign)
 	assert.Nil(t, rErr, "Error occurred during checksum verification.")
 	assert.True(t, replyOk, "Checksum verification failed.")
 }
@@ -124,7 +124,7 @@ func TestMICChecksumVerificationFailure(t *testing.T) {
 	mt.Unmarshal(challenge, true)
 
 	// Test a failure with the correct key but wrong keyusage:
-	challengeOk, cErr := mt.VerifyChecksum(getSessionKey(), initiatorSign)
+	challengeOk, cErr := mt.Verify(getSessionKey(), initiatorSign)
 	assert.NotNil(t, cErr, "Expected error did not occur.")
 	assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.")
 
@@ -134,7 +134,7 @@ func TestMICChecksumVerificationFailure(t *testing.T) {
 		KeyValue: wrongKeyVal,
 	}
 	// Test a failure with the wrong key but correct keyusage:
-	wrongKeyOk, wkErr := mt.VerifyChecksum(badKey, acceptorSign)
+	wrongKeyOk, wkErr := mt.Verify(badKey, acceptorSign)
 	assert.NotNil(t, wkErr, "Expected error did not occur.")
 	assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.")
 }

+ 0 - 9
gssapi/MechType.go

@@ -1,9 +0,0 @@
-package gssapi
-
-import "github.com/jcmturner/gofork/encoding/asn1"
-
-// MechTypeOIDKRB5 is the MechType OID for Kerberos 5
-var MechTypeOIDKRB5 = asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}
-
-// MechTypeOIDMSLegacyKRB5 is the MechType OID for MS legacy Kerberos 5
-var MechTypeOIDMSLegacyKRB5 = asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2}

+ 0 - 149
gssapi/NegotiationToken.go

@@ -1,149 +0,0 @@
-package gssapi
-
-import (
-	"errors"
-	"fmt"
-
-	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
-)
-
-/*
-https://msdn.microsoft.com/en-us/library/ms995330.aspx
-
-NegotiationToken ::= CHOICE {
-  negTokenInit    [0] NegTokenInit,  This is the Negotiation token sent from the client to the server.
-  negTokenResp    [1] NegTokenResp
-}
-
-NegTokenInit ::= SEQUENCE {
-  mechTypes       [0] MechTypeList,
-  reqFlags        [1] ContextFlags  OPTIONAL,
-  -- inherited from RFC 2478 for backward compatibility,
-  -- RECOMMENDED to be left out
-  mechToken       [2] OCTET STRING  OPTIONAL,
-  mechListMIC     [3] OCTET STRING  OPTIONAL,
-  ...
-}
-
-NegTokenResp ::= SEQUENCE {
-  negState       [0] ENUMERATED {
-    accept-completed    (0),
-    accept-incomplete   (1),
-    reject              (2),
-    request-mic         (3)
-  }                                 OPTIONAL,
-  -- REQUIRED in the first reply from the target
-  supportedMech   [1] MechType      OPTIONAL,
-  -- present only in the first reply from the target
-  responseToken   [2] OCTET STRING  OPTIONAL,
-  mechListMIC     [3] OCTET STRING  OPTIONAL,
-  ...
-}
-*/
-
-// NegTokenInit implements Negotiation Token of type Init
-type NegTokenInit struct {
-	MechTypes    []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
-	ReqFlags     ContextFlags            `asn1:"explicit,optional,tag:1"`
-	MechToken    []byte                  `asn1:"explicit,optional,tag:2"`
-	MechTokenMIC []byte                  `asn1:"explicit,optional,tag:3"`
-}
-
-// NegTokenResp implements Negotiation Token of type Resp/Targ
-type NegTokenResp struct {
-	NegState      asn1.Enumerated       `asn1:"explicit,tag:0"`
-	SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
-	ResponseToken []byte                `asn1:"explicit,optional,tag:2"`
-	MechListMIC   []byte                `asn1:"explicit,optional,tag:3"`
-}
-
-// NegTokenTarg implements Negotiation Token of type Resp/Targ
-type NegTokenTarg NegTokenResp
-
-// UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp.
-//
-// The boolean indicates if the response is a NegTokenInit.
-// If error is nil and the boolean is false the response is a NegTokenResp.
-func UnmarshalNegToken(b []byte) (bool, interface{}, error) {
-	var a asn1.RawValue
-	_, err := asn1.Unmarshal(b, &a)
-	if err != nil {
-		return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err)
-	}
-	switch a.Tag {
-	case 0:
-		var negToken NegTokenInit
-		_, err = asn1.Unmarshal(a.Bytes, &negToken)
-		if err != nil {
-			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err)
-		}
-		return true, negToken, nil
-	case 1:
-		var negToken NegTokenResp
-		_, err = asn1.Unmarshal(a.Bytes, &negToken)
-		if err != nil {
-			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err)
-		}
-		return false, negToken, nil
-	default:
-		return false, nil, errors.New("unknown choice type for NegotiationToken")
-	}
-
-}
-
-// Marshal an Init negotiation token
-func (n *NegTokenInit) Marshal() ([]byte, error) {
-	b, err := asn1.Marshal(*n)
-	if err != nil {
-		return nil, err
-	}
-	nt := asn1.RawValue{
-		Tag:        0,
-		Class:      2,
-		IsCompound: true,
-		Bytes:      b,
-	}
-	nb, err := asn1.Marshal(nt)
-	if err != nil {
-		return nil, err
-	}
-	return nb, nil
-}
-
-// Marshal a Resp/Targ negotiation token
-func (n *NegTokenResp) Marshal() ([]byte, error) {
-	b, err := asn1.Marshal(*n)
-	if err != nil {
-		return nil, err
-	}
-	nt := asn1.RawValue{
-		Tag:        1,
-		Class:      2,
-		IsCompound: true,
-		Bytes:      b,
-	}
-	nb, err := asn1.Marshal(nt)
-	if err != nil {
-		return nil, err
-	}
-	return nb, nil
-}
-
-// NewNegTokenInitKrb5 creates new Init negotiation token for Kerberos 5
-func NewNegTokenInitKrb5(creds credentials.Credentials, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) {
-	mt, err := NewAPREQMechToken(creds, tkt, sessionKey, []int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG}, []int{})
-	if err != nil {
-		return NegTokenInit{}, fmt.Errorf("error getting MechToken; %v", err)
-	}
-	mtb, err := mt.Marshal()
-	if err != nil {
-		return NegTokenInit{}, fmt.Errorf("error marshalling MechToken; %v", err)
-	}
-	return NegTokenInit{
-		MechTypes: []asn1.ObjectIdentifier{MechTypeOIDKRB5},
-		MechToken: mtb,
-	}, nil
-}

+ 25 - 0
gssapi/contextFlags.go

@@ -0,0 +1,25 @@
+package gssapi
+
+import "github.com/jcmturner/gofork/encoding/asn1"
+
+// GSS-API context flags assigned numbers.
+const (
+	ContextFlagDeleg    = 1
+	ContextFlagMutual   = 2
+	ContextFlagReplay   = 4
+	ContextFlagSequence = 8
+	ContextFlagConf     = 16
+	ContextFlagInteg    = 32
+	ContextFlagAnon     = 64
+)
+
+// ContextFlags flags for GSSAPI
+type ContextFlags asn1.BitString
+
+// NewContextFlags creates a new ContextFlags instance.
+func NewContextFlags() ContextFlags {
+	var c ContextFlags
+	c.BitLength = 32
+	c.Bytes = make([]byte, 4)
+	return c
+}

+ 179 - 82
gssapi/gssapi.go

@@ -2,101 +2,198 @@
 package gssapi
 
 import (
-	"errors"
+	"context"
 	"fmt"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
 )
 
-// SPNEGO_OID is the OID for SPNEGO header type.
-var SPNEGO_OID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
+// GSS-API OID names
+const (
+	// GSS-API OID names
+	OIDKRB5         OIDName = "KRB5"         // MechType OID for Kerberos 5
+	OIDMSLegacyKRB5 OIDName = "MSLegacyKRB5" // MechType OID for Kerberos 5
+	OIDSPNEGO       OIDName = "SPNEGO"
+)
+
+// GSS-API status values
+const (
+	StatusBadBindings = 1 << iota
+	StatusBadMech
+	StatusBadName
+	StatusBadNameType
+	StatusBadStatus
+	StatusBadSig
+	StatusBadMIC
+	StatusContextExpired
+	StatusCredentialsExpired
+	StatusDefectiveCredential
+	StatusDefectiveToken
+	StatusFailure
+	StatusNoContext
+	StatusNoCred
+	StatusBadQOP
+	StatusUnauthorized
+	StatusUnavailable
+	StatusDuplicateElement
+	StatusNameNotMN
+	StatusComplete
+	StatusContinueNeeded
+	StatusDuplicateToken
+	StatusOldToken
+	StatusUnseqToken
+	StatusGapToken
+)
 
-// SPNEGO header struct
-type SPNEGO struct {
-	Init         bool
-	Resp         bool
-	NegTokenInit NegTokenInit
-	NegTokenResp NegTokenResp
+// ContextToken is an interface for a GSS-API context token.
+type ContextToken interface {
+	Marshal() ([]byte, error)
+	Unmarshal(b []byte) error
+	Verify() (bool, Status)
+	Context() context.Context
 }
 
-// Unmarshal SPNEGO negotiation token
-func (s *SPNEGO) Unmarshal(b []byte) error {
-	var r []byte
-	var err error
-	if b[0] != byte(161) {
-		// Not a NegTokenResp/Targ could be a NegTokenInit
-		var oid asn1.ObjectIdentifier
-		r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
-		if err != nil {
-			return fmt.Errorf("not a valid SPNEGO token: %v", err)
-		}
-		// Check the OID is the SPNEGO OID value
-		if !oid.Equal(SPNEGO_OID) {
-			return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGO_OID.String())
-		}
-	} else {
-		// Could be a NegTokenResp/Targ
-		r = b
-	}
+/*
+CREDENTIAL MANAGEMENT
 
-	var a asn1.RawValue
-	_, err = asn1.Unmarshal(r, &a)
-	if err != nil {
-		return fmt.Errorf("error unmarshalling SPNEGO: %v", err)
-	}
-	switch a.Tag {
-	case 0:
-		_, err = asn1.Unmarshal(a.Bytes, &s.NegTokenInit)
-		if err != nil {
-			return fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err)
-		}
-		s.Init = true
-	case 1:
-		_, err = asn1.Unmarshal(a.Bytes, &s.NegTokenResp)
-		if err != nil {
-			return fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err)
-		}
-		s.Resp = true
-	default:
-		return errors.New("unknown choice type for NegotiationToken")
-	}
-	return nil
+GSS_Acquire_cred             acquire credentials for use
+GSS_Release_cred             release credentials after use
+GSS_Inquire_cred             display information about credentials
+GSS_Add_cred                 construct credentials incrementally
+GSS_Inquire_cred_by_mech     display per-mechanism credential information
+
+CONTEXT-LEVEL CALLS
+
+GSS_Init_sec_context         initiate outbound security context
+GSS_Accept_sec_context       accept inbound security context
+GSS_Delete_sec_context       flush context when no longer needed
+GSS_Process_context_token    process received control token on context
+GSS_Context_time             indicate validity time remaining on context
+GSS_Inquire_context          display information about context
+GSS_Wrap_size_limit          determine GSS_Wrap token size limit
+GSS_Export_sec_context       transfer context to other process
+GSS_Import_sec_context       import transferred context
+
+PER-MESSAGE CALLS
+
+GSS_GetMIC                   apply integrity check, receive as token separate from message
+GSS_VerifyMIC                validate integrity check token along with message
+GSS_Wrap                     sign, optionally encrypt, encapsulate
+GSS_Unwrap                   decapsulate, decrypt if needed, validate integrity check
+
+SUPPORT CALLS
+
+GSS_Display_status           translate status codes to printable form
+GSS_Indicate_mechs           indicate mech_types supported on local system
+GSS_Compare_name             compare two names for equality
+GSS_Display_name             translate name to printable form
+GSS_Import_name              convert printable name to normalized form
+GSS_Release_name             free storage of normalized-form name
+GSS_Release_buffer           free storage of general GSS-allocated object
+GSS_Release_OID_set          free storage of OID set object
+GSS_Create_empty_OID_set     create empty OID set
+GSS_Add_OID_set_member       add member to OID set
+GSS_Test_OID_set_member      test if OID is member of OID set
+GSS_Inquire_names_for_mech   indicate name types supported by mechanism
+GSS_Inquire_mechs_for_name   indicates mechanisms supporting name type
+GSS_Canonicalize_name        translate name to per-mechanism form
+GSS_Export_name              externalize per-mechanism name
+GSS_Duplicate_name           duplicate name object
+*/
+
+// Mechanism is the GSS-API interface for authentication mechanisms.
+type Mechanism interface {
+	OID() asn1.ObjectIdentifier
+	AcquireCred() error                                               // acquire credentials for use (eg. AS exchange for KRB5)
+	InitSecContext() (ContextToken, error)                            // initiate outbound security context (eg TGS exchange builds AP_REQ to go into ContextToken to send to service)
+	AcceptSecContext(ct ContextToken) (bool, context.Context, Status) // service verifies the token server side to establish a context
+	MIC() MICToken                                                    // apply integrity check, receive as token separate from message
+	VerifyMIC(mt MICToken) (bool, error)                              // validate integrity check token along with message
+	Wrap(msg []byte) WrapToken                                        // sign, optionally encrypt, encapsulate
+	Unwrap(wt WrapToken) []byte                                       // decapsulate, decrypt if needed, validate integrity check
 }
 
-// Marshal SPNEGO negotiation token
-func (s *SPNEGO) Marshal() ([]byte, error) {
-	var b []byte
-	if s.Init {
-		hb, _ := asn1.Marshal(SPNEGO_OID)
-		tb, err := s.NegTokenInit.Marshal()
-		if err != nil {
-			return b, fmt.Errorf("could not marshal NegTokenInit: %v", err)
-		}
-		b = append(hb, tb...)
-		return asn1tools.AddASNAppTag(b, 0), nil
-	}
-	if s.Resp {
-		b, err := s.NegTokenResp.Marshal()
-		if err != nil {
-			return b, fmt.Errorf("could not marshal NegTokenResp: %v", err)
-		}
-		return b, nil
+// OIDName is the type for defined GSS-API OIDs.
+type OIDName string
+
+// OID returns the OID for the provided OID name.
+func OID(o OIDName) asn1.ObjectIdentifier {
+	switch o {
+	case OIDSPNEGO:
+		return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
+	case OIDKRB5:
+		return asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}
+	case OIDMSLegacyKRB5:
+		return asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2}
 	}
-	return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp")
+	return asn1.ObjectIdentifier{}
+}
+
+// Status is the GSS-API status and implements the error interface.
+type Status struct {
+	Code    int
+	Message string
 }
 
-// GetSPNEGOKrbNegTokenInit returns an SPNEGO struct containing a NegTokenInit.
-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)
+// Error returns the Status description.
+func (s Status) Error() string {
+	var str string
+	switch s.Code {
+	case StatusBadBindings:
+		str = "channel binding mismatch"
+	case StatusBadMech:
+		str = "unsupported mechanism requested"
+	case StatusBadName:
+		str = "invalid name provided"
+	case StatusBadNameType:
+		str = "name of unsupported type provided"
+	case StatusBadStatus:
+		str = "invalid input status selector"
+	case StatusBadSig:
+		str = "token had invalid integrity check"
+	case StatusBadMIC:
+		str = "preferred alias for GSS_S_BAD_SIG"
+	case StatusContextExpired:
+		str = "specified security context expired"
+	case StatusCredentialsExpired:
+		str = "expired credentials detected"
+	case StatusDefectiveCredential:
+		str = "defective credential detected"
+	case StatusDefectiveToken:
+		str = "defective token detected"
+	case StatusFailure:
+		str = "failure, unspecified at GSS-API level"
+	case StatusNoContext:
+		str = "no valid security context specified"
+	case StatusNoCred:
+		str = "no valid credentials provided"
+	case StatusBadQOP:
+		str = "unsupported QOP valu"
+	case StatusUnauthorized:
+		str = "operation unauthorized"
+	case StatusUnavailable:
+		str = "operation unavailable"
+	case StatusDuplicateElement:
+		str = "duplicate credential element requested"
+	case StatusNameNotMN:
+		str = "name contains multi-mechanism elements"
+	case StatusComplete:
+		str = "normal completion"
+	case StatusContinueNeeded:
+		str = "continuation call to routine required"
+	case StatusDuplicateToken:
+		str = "duplicate per-message token detected"
+	case StatusOldToken:
+		str = "timed-out per-message token detected"
+	case StatusUnseqToken:
+		str = "reordered (early) per-message token detected"
+	case StatusGapToken:
+		str = "skipped predecessor token(s) detected"
+	default:
+		str = "unknown GSS-API error status"
+	}
+	if s.Message != "" {
+		return fmt.Sprintf("%s: %s", str, s.Message)
 	}
-	return SPNEGO{
-		Init:         true,
-		NegTokenInit: negTokenInit,
-	}, nil
+	return str
 }

+ 11 - 78
gssapi/gssapi_test.go

@@ -1,91 +1,24 @@
 package gssapi
 
 import (
-	"encoding/hex"
 	"testing"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
 	"github.com/stretchr/testify/assert"
 )
 
-const (
-	testGSSAPIInit = "608202b606062b0601050502a08202aa308202a6a027302506092a864886f71201020206052b0501050206092a864882f71201020206062b0601050205a2820279048202756082027106092a864886f71201020201006e8202603082025ca003020105a10302010ea20703050000000000a38201706182016c30820168a003020105a10d1b0b544553542e474f4b524235a2233021a003020103a11a30181b04485454501b10686f73742e746573742e676f6b726235a382012b30820127a003020112a103020102a282011904820115d4bd890abc456f44e2e7a2e8111bd6767abf03266dfcda97c629af2ece450a5ae1f145e4a4d1bc2c848e66a6c6b31d9740b26b03cdbd2570bfcf126e90adf5f5ebce9e283ff5086da47b129b14fc0aabd4d1df9c1f3c72b80cc614dfc28783450b2c7b7749651f432b47aaa2ff158c0066b757f3fb00dd7b4f63d68276c76373ecdd3f19c66ebc43a81e577f3c263b878356f57e8d6c4eccd587b81538e70392cf7e73fc12a6f7c537a894a7bb5566c83ac4d69757aa320a51d8d690017aebf952add1889adfc3307b0e6cd8c9b57cf8589fbe52800acb6461c25473d49faa1bdceb8bce3f61db23f9cd6a09d5adceb411e1c4546b30b33331e570fd6bc50aa403557e75f488e759750ea038aab6454667d9b64f41a481d23081cfa003020112a281c70481c4eb593beb5afcb1a2a669d54cb85a3772231559f2d40c9f8f053f218ba6eb084ed7efc467d94b88bcd189dda920d6e675ec001a6a2bca11f0a1de37f2f7ae9929f94a86d625b2ec1b213a88cbae6099dda7b172cd3bd1802cb177ae4554d59277004bfd3435248f55044fe7af7b2c9c5a3c43763278c585395aebe2856cdff9f2569d8b823564ce6be2d19748b910ec06bd3c0a9bc5de51ddcf7d875f1108ca6ad935f52d90cb62a18197d9b8e796bef0fbe1463f61df61cfbce6008ae9e1a2d2314a986d"
-	testGSSAPIResp = "a1143012a0030a0100a10b06092a864886f712010202"
-)
-
-func TestUnmarshal_SPNEGO_Init(t *testing.T) {
-	t.Parallel()
-	b, err := hex.DecodeString(testGSSAPIInit)
-	if err != nil {
-		t.Fatalf("Error converting hex string test data to bytes: %v", err)
-	}
-	var s SPNEGO
-	err = s.Unmarshal(b)
-	if err != nil {
-		t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err)
-	}
-	assert.True(t, s.Init, "SPNEGO does not indicate it contains NegTokenInit as expected")
-	assert.False(t, s.Resp, "SPNEGO indicates is contains a NegTokenResp but it shouldn't")
-	assert.Equal(t, 4, len(s.NegTokenInit.MechTypes))
-	expectMechTypes := []asn1.ObjectIdentifier{
-		MechTypeOIDKRB5,
-		[]int{1, 3, 5, 1, 5, 2},
-		MechTypeOIDMSLegacyKRB5,
-		[]int{1, 3, 6, 1, 5, 2, 5},
-	}
-	assert.Equal(t, expectMechTypes, s.NegTokenInit.MechTypes, "MechTypes list in NegTokenInit not as expected")
-	assert.NotZero(t, len(s.NegTokenInit.MechToken), "MechToken is zero in length")
-}
-
-func TestUnmarshal_SPNEGO_RespTarg(t *testing.T) {
-	t.Parallel()
-	b, err := hex.DecodeString(testGSSAPIResp)
-	if err != nil {
-		t.Fatalf("Error converting hex string test data to bytes: %v", err)
-	}
-	var s SPNEGO
-	err = s.Unmarshal(b)
-	if err != nil {
-		t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp/NegTokenTarg: %v", err)
+func TestOID(t *testing.T) {
+	var tests = []struct {
+		name OIDName
+		oid  []int
+	}{
+		{OIDMSLegacyKRB5, []int{1, 2, 840, 48018, 1, 2, 2}},
+		{OIDKRB5, []int{1, 2, 840, 113554, 1, 2, 2}},
+		{OIDSPNEGO, []int{1, 3, 6, 1, 5, 5, 2}},
 	}
-	assert.True(t, s.Resp, "SPNEGO does not indicate it contains NegTokenResp/Targ as expected")
-	assert.False(t, s.Init, "SPNEGO indicates is contains a NegTokenInit but it shouldn't")
-	assert.Equal(t, asn1.Enumerated(0), s.NegTokenResp.NegState, "Negotiation state not as expected.")
-	assert.Equal(t, MechTypeOIDKRB5, s.NegTokenResp.SupportedMech, "SupportedMech type not as expected.")
-}
-
-func TestMarshal_SPNEGO_Init(t *testing.T) {
-	t.Parallel()
-	b, err := hex.DecodeString(testGSSAPIInit)
-	if err != nil {
-		t.Fatalf("Error converting hex string test data to bytes: %v", err)
-	}
-	var s SPNEGO
-	err = s.Unmarshal(b)
-	if err != nil {
-		t.Fatalf("Error unmarshalling SPNEGO with NegTokenInit: %v", err)
-	}
-	mb, err := s.Marshal()
-	if err != nil {
-		t.Fatalf("Error marshalling SPNEGO containing NegTokenInit: %v", err)
-	}
-	assert.Equal(t, b, mb, "Marshaled bytes not as expected")
-}
 
-func TestMarshal_SPNEGO_RespTarg(t *testing.T) {
-	t.Parallel()
-	b, err := hex.DecodeString(testGSSAPIResp)
-	if err != nil {
-		t.Fatalf("Error converting hex string test data to bytes: %v", err)
-	}
-	var s SPNEGO
-	err = s.Unmarshal(b)
-	if err != nil {
-		t.Fatalf("Error unmarshalling SPNEGO with NegTokenResp: %v", err)
-	}
-	mb, err := s.Marshal()
-	if err != nil {
-		t.Fatalf("Error marshalling SPNEGO containing NegTokenResp: %v", err)
+	for _, tst := range tests {
+		oid := asn1.ObjectIdentifier(tst.oid)
+		assert.True(t, oid.Equal(OID(tst.name)), "OID value not as expected for %s", tst.name)
 	}
-	assert.Equal(t, b, mb, "Marshaled bytes not as expected")
 }

+ 10 - 10
gssapi/WrapToken.go → gssapi/wrapToken.go

@@ -8,9 +8,9 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 /*
@@ -105,14 +105,14 @@ func (wt *WrapToken) Marshal() ([]byte, error) {
 // ComputeAndSetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and
 // the header, and sets the CheckSum field of this WrapToken.
 // If the payload has not been set or the checksum has already been set, an error is returned.
-func (wt *WrapToken) ComputeAndSetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
+func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
 	if wt.Payload == nil {
 		return errors.New("payload has not been set")
 	}
 	if wt.CheckSum != nil {
 		return errors.New("checksum has already been computed")
 	}
-	chkSum, cErr := wt.ComputeCheckSum(key, keyUsage)
+	chkSum, cErr := wt.computeCheckSum(key, keyUsage)
 	if cErr != nil {
 		return cErr
 	}
@@ -126,7 +126,7 @@ func (wt *WrapToken) ComputeAndSetCheckSum(key types.EncryptionKey, keyUsage uin
 // In the context of Kerberos Wrap tokens, mostly keyusage GSSAPI_ACCEPTOR_SEAL (=22)
 // and GSSAPI_INITIATOR_SEAL (=24) will be used.
 // Note: This will NOT update the struct's Checksum field.
-func (wt *WrapToken) ComputeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
+func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
 	if wt.Payload == nil {
 		return nil, errors.New("cannot compute checksum with uninitialized payload")
 	}
@@ -153,8 +153,8 @@ func getChecksumHeader(flags byte, senderSeqNum uint64) []byte {
 // VerifyCheckSum computes the token's checksum with the provided key and usage,
 // and compares it to the checksum present in the token.
 // In case of any failure, (false, Err) is returned, with Err an explanatory error.
-func (wt *WrapToken) VerifyCheckSum(key types.EncryptionKey, keyUsage uint32) (bool, error) {
-	computed, cErr := wt.ComputeCheckSum(key, keyUsage)
+func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
+	computed, cErr := wt.computeCheckSum(key, keyUsage)
 	if cErr != nil {
 		return false, cErr
 	}
@@ -212,7 +212,7 @@ func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
 // Other flags are set to 0, and the RRC and sequence number are initialized to 0.
 // Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
 // This is currently not supported.
-func NewInitiatorToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
+func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
 	encType, err := crypto.GetEtype(key.KeyType)
 	if err != nil {
 		return nil, err
@@ -227,7 +227,7 @@ func NewInitiatorToken(payload []byte, key types.EncryptionKey) (*WrapToken, err
 		Payload:   payload,
 	}
 
-	if err := token.ComputeAndSetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
+	if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
 		return nil, err
 	}
 

+ 7 - 7
gssapi/WrapToken_test.go → gssapi/wrapToken_test.go

@@ -6,8 +6,8 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -118,7 +118,7 @@ func TestChallengeChecksumVerification(t *testing.T) {
 	challenge, _ := hex.DecodeString(testChallengeFromAcceptor)
 	var wt WrapToken
 	wt.Unmarshal(challenge, true)
-	challengeOk, cErr := wt.VerifyCheckSum(getSessionKey(), acceptorSeal)
+	challengeOk, cErr := wt.Verify(getSessionKey(), acceptorSeal)
 	assert.Nil(t, cErr, "Error occurred during checksum verification.")
 	assert.True(t, challengeOk, "Checksum verification failed.")
 }
@@ -128,7 +128,7 @@ func TestResponseChecksumVerification(t *testing.T) {
 	reply, _ := hex.DecodeString(testChallengeReplyFromInitiator)
 	var wt WrapToken
 	wt.Unmarshal(reply, false)
-	replyOk, rErr := wt.VerifyCheckSum(getSessionKey(), initiatorSeal)
+	replyOk, rErr := wt.Verify(getSessionKey(), initiatorSeal)
 	assert.Nil(t, rErr, "Error occurred during checksum verification.")
 	assert.True(t, replyOk, "Checksum verification failed.")
 }
@@ -140,7 +140,7 @@ func TestChecksumVerificationFailure(t *testing.T) {
 	wt.Unmarshal(challenge, true)
 
 	// Test a failure with the correct key but wrong keyusage:
-	challengeOk, cErr := wt.VerifyCheckSum(getSessionKey(), initiatorSeal)
+	challengeOk, cErr := wt.Verify(getSessionKey(), initiatorSeal)
 	assert.NotNil(t, cErr, "Expected error did not occur.")
 	assert.False(t, challengeOk, "Checksum verification succeeded when it should have failed.")
 
@@ -150,7 +150,7 @@ func TestChecksumVerificationFailure(t *testing.T) {
 		KeyValue: wrongKeyVal,
 	}
 	// Test a failure with the wrong key but correct keyusage:
-	wrongKeyOk, wkErr := wt.VerifyCheckSum(badKey, acceptorSeal)
+	wrongKeyOk, wkErr := wt.Verify(badKey, acceptorSeal)
 	assert.NotNil(t, wkErr, "Expected error did not occur.")
 	assert.False(t, wrongKeyOk, "Checksum verification succeeded when it should have failed.")
 }
@@ -185,7 +185,7 @@ func TestMarshal_Failures(t *testing.T) {
 
 func TestNewInitiatorTokenSignatureAndMarshalling(t *testing.T) {
 	t.Parallel()
-	token, tErr := NewInitiatorToken([]byte{0x01, 0x01, 0x00, 0x00}, getSessionKey())
+	token, tErr := NewInitiatorWrapToken([]byte{0x01, 0x01, 0x00, 0x00}, getSessionKey())
 	assert.Nil(t, tErr, "Unexpected error.")
 	assert.Equal(t, getResponseReference(), token, "Token failed to be marshalled to the expected bytes.")
 }

+ 6 - 0
iana/flags/constants.go

@@ -27,4 +27,10 @@ const (
 	EncTktInSkey           = 28
 	Renew                  = 30
 	Validate               = 31
+
+	// AP Option Flags
+	// 0 Reserved for future use.
+	APOptionUseSessionKey  = 1
+	APOptionMutualRequired = 2
+	// 3-31 Reserved for future use.
 )

+ 1 - 1
kadmin/changepasswddata.go

@@ -2,7 +2,7 @@ package kadmin
 
 import (
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // ChangePasswdData is the payload to a password change message.

+ 5 - 6
kadmin/changepasswddata_test.go

@@ -5,9 +5,9 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 func TestChangePasswdData_Marshal(t *testing.T) {
@@ -21,10 +21,9 @@ func TestChangePasswdData_Marshal(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error marshaling change passwd data: %v\n", err)
 	}
-	v := "ChangePasswdData"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledChangePasswdData)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	assert.Equal(t, b, chpwdb, "marshaled bytes of change passwd data not as expected")
 }

+ 2 - 2
kadmin/message.go

@@ -7,8 +7,8 @@ import (
 	"fmt"
 	"math"
 
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (

+ 6 - 7
kadmin/message_test.go

@@ -5,22 +5,21 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalReply(t *testing.T) {
 	t.Parallel()
 	var a Reply
-	v := "Kpasswd_Rep"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKpasswd_Rep)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, 236, a.MessageLength, "message length not as expected")
 	assert.Equal(t, 1, a.Version, "message version not as expected")

+ 4 - 4
kadmin/passwd.go

@@ -2,10 +2,10 @@
 package kadmin
 
 import (
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // ChangePasswdMsg generate a change password request and also return the key needed to decrypt the reply.

+ 28 - 29
keytab/keytab.go

@@ -11,7 +11,7 @@ import (
 	"time"
 	"unsafe"
 
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
@@ -20,7 +20,7 @@ const (
 
 // Keytab struct.
 type Keytab struct {
-	Version uint8
+	version uint8
 	Entries []entry
 }
 
@@ -42,27 +42,27 @@ type principal struct {
 }
 
 // NewKeytab creates new, empty Keytab type.
-func NewKeytab() Keytab {
+func New() *Keytab {
 	var e []entry
-	return Keytab{
-		Version: 0,
+	return &Keytab{
+		version: 0,
 		Entries: e,
 	}
 }
 
 // GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal.
-func (kt *Keytab) GetEncryptionKey(nameString []string, realm string, kvno int, etype int32) (types.EncryptionKey, error) {
+func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, error) {
 	var key types.EncryptionKey
 	var t time.Time
 	for _, k := range kt.Entries {
-		if k.Principal.Realm == realm && len(k.Principal.Components) == len(nameString) &&
+		if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) &&
 			k.Key.KeyType == etype &&
 			(k.KVNO == uint32(kvno) || kvno == 0) &&
 			k.Timestamp.After(t) {
 
 			p := true
 			for i, n := range k.Principal.Components {
-				if nameString[i] != n {
+				if princName.NameString[i] != n {
 					p = false
 					break
 				}
@@ -74,7 +74,7 @@ func (kt *Keytab) GetEncryptionKey(nameString []string, realm string, kvno int,
 		}
 	}
 	if len(key.KeyValue) < 1 {
-		return key, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", nameString, realm, kvno, etype)
+		return key, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", princName.NameString, realm, kvno, etype)
 	}
 	return key, nil
 }
@@ -106,19 +106,20 @@ func newPrincipal() principal {
 }
 
 // Load a Keytab file into a Keytab type.
-func Load(ktPath string) (kt Keytab, err error) {
-	k, err := ioutil.ReadFile(ktPath)
+func Load(ktPath string) (kt *Keytab, err error) {
+	b, err := ioutil.ReadFile(ktPath)
 	if err != nil {
 		return
 	}
-	return Parse(k)
+	err = kt.Unmarshal(b)
+	return
 }
 
 // Marshal keytab into byte slice
-func (kt Keytab) Marshal() ([]byte, error) {
-	b := []byte{keytabFirstByte, kt.Version}
+func (kt *Keytab) Marshal() ([]byte, error) {
+	b := []byte{keytabFirstByte, kt.version}
 	for _, e := range kt.Entries {
-		eb, err := e.marshal(int(kt.Version))
+		eb, err := e.marshal(int(kt.version))
 		if err != nil {
 			return b, err
 		}
@@ -129,7 +130,7 @@ func (kt Keytab) Marshal() ([]byte, error) {
 
 // Write the keytab bytes to io.Writer.
 // Returns the number of bytes written
-func (kt Keytab) Write(w io.Writer) (int, error) {
+func (kt *Keytab) Write(w io.Writer) (int, error) {
 	b, err := kt.Marshal()
 	if err != nil {
 		return 0, fmt.Errorf("error marshaling keytab: %v", err)
@@ -137,24 +138,22 @@ func (kt Keytab) Write(w io.Writer) (int, error) {
 	return w.Write(b)
 }
 
-// Parse byte slice of Keytab data into Keytab type.
-func Parse(b []byte) (kt Keytab, err error) {
+// Unmarshal byte slice of Keytab data into Keytab type.
+func (kt *Keytab) Unmarshal(b []byte) error {
 	//The first byte of the file always has the value 5
 	if b[0] != keytabFirstByte {
-		err = errors.New("invalid keytab data. First byte does not equal 5")
-		return
+		return errors.New("invalid keytab data. First byte does not equal 5")
 	}
 	//Get keytab version
 	//The 2nd byte contains the version number (1 or 2)
-	kt.Version = b[1]
-	if kt.Version != 1 && kt.Version != 2 {
-		err = errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
-		return
+	kt.version = b[1]
+	if kt.version != 1 && kt.version != 2 {
+		return errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
 	}
 	//Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
 	var endian binary.ByteOrder
 	endian = binary.BigEndian
-	if kt.Version == 1 && isNativeEndianLittle() {
+	if kt.version == 1 && isNativeEndianLittle() {
 		endian = binary.LittleEndian
 	}
 	/*
@@ -178,7 +177,7 @@ func Parse(b []byte) (kt Keytab, err error) {
 			ke := newKeytabEntry()
 			// p keeps track as to where we are in the byte stream
 			var p int
-			parsePrincipal(eb, &p, &kt, &ke, &endian)
+			parsePrincipal(eb, &p, kt, &ke, &endian)
 			ke.Timestamp = readTimestamp(eb, &p, &endian)
 			ke.KVNO8 = uint8(readInt8(eb, &p, &endian))
 			ke.Key.KeyType = int32(readInt16(eb, &p, &endian))
@@ -205,7 +204,7 @@ func Parse(b []byte) (kt Keytab, err error) {
 		// Read the size of the next entry
 		l = readInt32(b, &n, &endian)
 	}
-	return
+	return nil
 }
 
 func (e entry) marshal(v int) ([]byte, error) {
@@ -250,7 +249,7 @@ func (e entry) marshal(v int) ([]byte, error) {
 // Parse the Keytab bytes of a principal into a Keytab entry's principal.
 func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) error {
 	ke.Principal.NumComponents = readInt16(b, p, e)
-	if kt.Version == 1 {
+	if kt.version == 1 {
 		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
 		ke.Principal.NumComponents--
 	}
@@ -260,7 +259,7 @@ func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder
 		l := readInt16(b, p, e)
 		ke.Principal.Components = append(ke.Principal.Components, string(readBytes(b, p, int(l), e)))
 	}
-	if kt.Version != 1 {
+	if kt.version != 1 {
 		//Name Type is omitted in version 1
 		ke.Principal.NameType = readInt32(b, p, e)
 	}

+ 12 - 10
keytab/keytab_test.go

@@ -6,17 +6,18 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
-func TestParse(t *testing.T) {
+func TestUnmarshal(t *testing.T) {
 	t.Parallel()
-	dat, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, err := Parse(dat)
+	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt := New()
+	err := kt.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing keytab data: %v\n", err)
 	}
-	assert.Equal(t, uint8(2), kt.Version, "Keytab version not as expected")
+	assert.Equal(t, uint8(2), kt.version, "Keytab version not as expected")
 	assert.Equal(t, uint32(1), kt.Entries[0].KVNO, "KVNO not as expected")
 	assert.Equal(t, uint8(1), kt.Entries[0].KVNO8, "KVNO8 not as expected")
 	assert.Equal(t, time.Unix(1505669592, 0), kt.Entries[0].Timestamp, "Timestamp not as expected")
@@ -30,17 +31,18 @@ func TestParse(t *testing.T) {
 
 func TestMarshal(t *testing.T) {
 	t.Parallel()
-	dat, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, err := Parse(dat)
+	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt := New()
+	err := kt.Unmarshal(b)
 	if err != nil {
 		t.Fatalf("Error parsing keytab data: %v\n", err)
 	}
-	b, err := kt.Marshal()
+	mb, err := kt.Marshal()
 	if err != nil {
 		t.Fatalf("Error marshaling: %v", err)
 	}
-	assert.Equal(t, dat, b, "Marshaled bytes not the same as input bytes")
-	_, err = Parse(b)
+	assert.Equal(t, b, mb, "Marshaled bytes not the same as input bytes")
+	err = kt.Unmarshal(mb)
 	if err != nil {
 		t.Fatalf("Error parsing marshaled bytes: %v", err)
 	}

+ 4 - 4
messages/APRep.go

@@ -5,10 +5,10 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 /*

+ 12 - 15
messages/APRep_test.go

@@ -6,22 +6,21 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalAPRep(t *testing.T) {
 	t.Parallel()
 	var a APRep
-	v := "encode_krb5_ap_rep"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_AP_REP, a.MsgType, "MsgType is not as expected")
@@ -33,14 +32,13 @@ func TestUnmarshalAPRep(t *testing.T) {
 func TestUnmarshalEncAPRepPart(t *testing.T) {
 	t.Parallel()
 	var a EncAPRepPart
-	v := "encode_krb5_ap_rep_enc_part"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -55,14 +53,13 @@ func TestUnmarshalEncAPRepPart(t *testing.T) {
 func TestUnmarshalEncAPRepPart_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a EncAPRepPart
-	v := "encode_krb5_ap_rep_enc_part(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ap_rep_enc_partOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)

+ 106 - 36
messages/APReq.go

@@ -2,16 +2,19 @@ package messages
 
 import (
 	"fmt"
+	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 /*AP-REQ          ::= [APPLICATION 14] SEQUENCE {
@@ -32,17 +35,18 @@ type marshalAPReq struct {
 	MsgType   int            `asn1:"explicit,tag:1"`
 	APOptions asn1.BitString `asn1:"explicit,tag:2"`
 	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
-	Ticket        asn1.RawValue       `asn1:"explicit,tag:3"`
-	Authenticator types.EncryptedData `asn1:"explicit,tag:4"`
+	Ticket                 asn1.RawValue       `asn1:"explicit,tag:3"`
+	EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"`
 }
 
 // APReq implements RFC 4120 KRB_AP_REQ: https://tools.ietf.org/html/rfc4120#section-5.5.1.
 type APReq struct {
-	PVNO          int                 `asn1:"explicit,tag:0"`
-	MsgType       int                 `asn1:"explicit,tag:1"`
-	APOptions     asn1.BitString      `asn1:"explicit,tag:2"`
-	Ticket        Ticket              `asn1:"explicit,tag:3"`
-	Authenticator types.EncryptedData `asn1:"explicit,tag:4"`
+	PVNO                   int                 `asn1:"explicit,tag:0"`
+	MsgType                int                 `asn1:"explicit,tag:1"`
+	APOptions              asn1.BitString      `asn1:"explicit,tag:2"`
+	Ticket                 Ticket              `asn1:"explicit,tag:3"`
+	EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"`
+	Authenticator          types.Authenticator `asn1:"optional"`
 }
 
 // NewAPReq generates a new KRB_AP_REQ struct.
@@ -53,11 +57,11 @@ func NewAPReq(tkt Ticket, sessionKey types.EncryptionKey, auth types.Authenticat
 		return a, krberror.Errorf(err, krberror.KRBMsgError, "error creating Authenticator for AP_REQ")
 	}
 	a = APReq{
-		PVNO:          iana.PVNO,
-		MsgType:       msgtype.KRB_AP_REQ,
-		APOptions:     types.NewKrbFlags(),
-		Ticket:        tkt,
-		Authenticator: ed,
+		PVNO:                   iana.PVNO,
+		MsgType:                msgtype.KRB_AP_REQ,
+		APOptions:              types.NewKrbFlags(),
+		Ticket:                 tkt,
+		EncryptedAuthenticator: ed,
 	}
 	return a, nil
 }
@@ -79,19 +83,17 @@ func encryptAuthenticator(a types.Authenticator, sessionKey types.EncryptionKey,
 
 // DecryptAuthenticator decrypts the Authenticator within the AP_REQ.
 // sessionKey may simply be the key within the decrypted EncPart of the ticket within the AP_REQ.
-func (a *APReq) DecryptAuthenticator(sessionKey types.EncryptionKey) (auth types.Authenticator, err error) {
+func (a *APReq) DecryptAuthenticator(sessionKey types.EncryptionKey) error {
 	usage := authenticatorKeyUsage(a.Ticket.SName)
-	ab, e := crypto.DecryptEncPart(a.Authenticator, sessionKey, uint32(usage))
+	ab, e := crypto.DecryptEncPart(a.EncryptedAuthenticator, sessionKey, uint32(usage))
 	if e != nil {
-		err = fmt.Errorf("error decrypting authenticator: %v", e)
-		return
+		return fmt.Errorf("error decrypting authenticator: %v", e)
 	}
-	e = auth.Unmarshal(ab)
-	if e != nil {
-		err = fmt.Errorf("error unmarshaling authenticator")
-		return
+	err := a.Authenticator.Unmarshal(ab)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling authenticator: %v", err)
 	}
-	return
+	return nil
 }
 
 func authenticatorKeyUsage(pn types.PrincipalName) int {
@@ -109,13 +111,13 @@ func (a *APReq) Unmarshal(b []byte) error {
 		return krberror.Errorf(err, krberror.EncodingError, "unmarshal error of AP_REQ")
 	}
 	if m.MsgType != msgtype.KRB_AP_REQ {
-		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AP_REQ. Expected: %v; Actual: %v", msgtype.KRB_AP_REQ, m.MsgType)
+		return NewKRBError(types.PrincipalName{}, "", errorcode.KRB_AP_ERR_MSG_TYPE, errorcode.Lookup(errorcode.KRB_AP_ERR_MSG_TYPE))
 	}
 	a.PVNO = m.PVNO
 	a.MsgType = m.MsgType
 	a.APOptions = m.APOptions
-	a.Authenticator = m.Authenticator
-	a.Ticket, err = UnmarshalTicket(m.Ticket.Bytes)
+	a.EncryptedAuthenticator = m.EncryptedAuthenticator
+	a.Ticket, err = unmarshalTicket(m.Ticket.Bytes)
 	if err != nil {
 		return krberror.Errorf(err, krberror.EncodingError, "unmarshaling error of Ticket within AP_REQ")
 	}
@@ -125,10 +127,10 @@ func (a *APReq) Unmarshal(b []byte) error {
 // Marshal APReq struct.
 func (a *APReq) Marshal() ([]byte, error) {
 	m := marshalAPReq{
-		PVNO:          a.PVNO,
-		MsgType:       a.MsgType,
-		APOptions:     a.APOptions,
-		Authenticator: a.Authenticator,
+		PVNO:                   a.PVNO,
+		MsgType:                a.MsgType,
+		APOptions:              a.APOptions,
+		EncryptedAuthenticator: a.EncryptedAuthenticator,
 	}
 	var b []byte
 	b, err := a.Ticket.Marshal()
@@ -148,3 +150,71 @@ func (a *APReq) Marshal() ([]byte, error) {
 	mk = asn1tools.AddASNAppTag(mk, asnAppTag.APREQ)
 	return mk, nil
 }
+
+// Verify an AP_REQ using service's keytab, spn and max acceptable clock skew duration.
+// The service ticket encrypted part and authenticator will be decrypted as part of this operation.
+func (a *APReq) Verify(kt *keytab.Keytab, d time.Duration, cAddr types.HostAddress) (bool, error) {
+	// Decrypt ticket's encrypted part with service key
+	//TODO decrypt with service's session key from its TGT is use-to-user. Need to figure out how to get TGT.
+	//if types.IsFlagSet(&a.APOptions, flags.APOptionUseSessionKey) {
+	//	//If the USE-SESSION-KEY flag is set in the ap-options field, it indicates to
+	//	//the server that user-to-user authentication is in use, and that the ticket
+	//	//is encrypted in the session key from the server's TGT rather than in the server's secret key.
+	//	err := a.Ticket.Decrypt(tgt.DecryptedEncPart.Key)
+	//	if err != nil {
+	//		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of ticket provided using session key")
+	//	}
+	//} else {
+	//	// Because it is possible for the server to be registered in multiple
+	//	// realms, with different keys in each, the srealm field in the
+	//	// unencrypted portion of the ticket in the KRB_AP_REQ is used to
+	//	// specify which secret key the server should use to decrypt that
+	//	// ticket.The KRB_AP_ERR_NOKEY error code is returned if the server
+	//	// doesn't have the proper key to decipher the ticket.
+	//	// The ticket is decrypted using the version of the server's key
+	//	// specified by the ticket.
+	//	err := a.Ticket.DecryptEncPart(*kt, &a.Ticket.SName)
+	//	if err != nil {
+	//		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
+	//	}
+	//}
+	err := a.Ticket.DecryptEncPart(kt, &a.Ticket.SName)
+	if err != nil {
+		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
+	}
+
+	// Check time validity of ticket
+	ok, err := a.Ticket.Valid(d)
+	if err != nil || !ok {
+		return ok, err
+	}
+
+	// Check client's address is listed in the client addresses in the ticket
+	if len(a.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.
+		if !types.HostAddressesContains(a.Ticket.DecryptedEncPart.CAddr, cAddr) {
+			return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "client address not within the list contained in the service ticket")
+		}
+	}
+
+	// Decrypt authenticator with session key from ticket's encrypted part
+	err = a.DecryptAuthenticator(a.Ticket.DecryptedEncPart.Key)
+	if err != nil {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt authenticator")
+	}
+
+	// Check CName in authenticator is the same as that in the ticket
+	if !a.Authenticator.CName.Equal(a.Ticket.DecryptedEncPart.CName) {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket")
+	}
+
+	// Check the clock skew between the client and the service server
+	ct := a.Authenticator.CTime.Add(time.Duration(a.Authenticator.Cusec) * time.Microsecond)
+	t := time.Now().UTC()
+	if t.Sub(ct) > d || ct.Sub(t) > d {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("clock skew with client too large. greater than %v seconds", d))
+	}
+	return true, nil
+}

+ 10 - 12
messages/APReq_test.go

@@ -5,23 +5,22 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalAPReq(t *testing.T) {
 	t.Parallel()
 	var a APReq
-	v := "encode_krb5_ap_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_AP_REQ, a.MsgType, "MsgType is not as expected")
@@ -39,14 +38,13 @@ func TestUnmarshalAPReq(t *testing.T) {
 func TestMarshalAPReq(t *testing.T) {
 	t.Parallel()
 	var a APReq
-	v := "encode_krb5_ap_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ap_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err := a.Marshal()
 	if err != nil {

+ 16 - 16
messages/KDCRep.go

@@ -8,16 +8,16 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 type marshalKDCRep struct {
@@ -87,7 +87,7 @@ func (k *ASRep) Unmarshal(b []byte) error {
 		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AS_REP. Expected: %v; Actual: %v", msgtype.KRB_AS_REP, m.MsgType)
 	}
 	//Process the raw ticket within
-	tkt, err := UnmarshalTicket(m.Ticket.Bytes)
+	tkt, err := unmarshalTicket(m.Ticket.Bytes)
 	if err != nil {
 		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within AS_REP")
 	}
@@ -114,7 +114,7 @@ func (k *TGSRep) Unmarshal(b []byte) error {
 		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an TGS_REP. Expected: %v; Actual: %v", msgtype.KRB_TGS_REP, m.MsgType)
 	}
 	//Process the raw ticket within
-	tkt, err := UnmarshalTicket(m.Ticket.Bytes)
+	tkt, err := unmarshalTicket(m.Ticket.Bytes)
 	if err != nil {
 		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within TGS_REP")
 	}
@@ -154,13 +154,13 @@ func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey,
 	var key types.EncryptionKey
 	var err error
 	if c.HasKeytab() {
-		key, err = c.Keytab.GetEncryptionKey(k.CName.NameString, k.CRealm, k.EncPart.KVNO, k.EncPart.EType)
+		key, err = c.Keytab().GetEncryptionKey(k.CName, k.CRealm, k.EncPart.KVNO, k.EncPart.EType)
 		if err != nil {
 			return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
 		}
 	}
 	if c.HasPassword() {
-		key, _, err = crypto.GetKeyFromPassword(c.Password, k.CName, k.CRealm, k.EncPart.EType, k.PAData)
+		key, _, err = crypto.GetKeyFromPassword(c.Password(), k.CName, k.CRealm, k.EncPart.EType, k.PAData)
 		if err != nil {
 			return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
 		}
@@ -182,7 +182,7 @@ func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey,
 }
 
 // IsValid checks the validity of AS_REP message.
-func (k *ASRep) IsValid(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) {
+func (k *ASRep) Verify(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) {
 	//Ref RFC 4120 Section 3.1.5
 	if k.CName.NameType != asReq.ReqBody.CName.NameType || k.CName.NameString == nil {
 		return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName)
@@ -264,7 +264,7 @@ func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error {
 }
 
 // IsValid checks the validity of the TGS_REP message.
-func (k *TGSRep) IsValid(cfg *config.Config, tgsReq TGSReq) (bool, error) {
+func (k *TGSRep) Verify(cfg *config.Config, tgsReq TGSReq) (bool, error) {
 	if k.CName.NameType != tgsReq.ReqBody.CName.NameType || k.CName.NameString == nil {
 		return false, krberror.NewErrorf(krberror.KRBMsgError, "CName type in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName)
 	}

+ 30 - 35
messages/KDCRep_test.go

@@ -7,14 +7,14 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/etypeID"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 const (
@@ -28,14 +28,13 @@ const (
 func TestUnmarshalASRep(t *testing.T) {
 	t.Parallel()
 	var a ASRep
-	v := "encode_krb5_as_rep"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_rep)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected")
@@ -64,14 +63,13 @@ func TestUnmarshalASRep(t *testing.T) {
 func TestUnmarshalASRep_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a ASRep
-	v := "encode_krb5_as_rep(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_repOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_AS_REP, a.MsgType, "MsgType not as expected")
@@ -96,14 +94,13 @@ func TestUnmarshalASRep_optionalsNULL(t *testing.T) {
 func TestUnmarshalTGSRep(t *testing.T) {
 	t.Parallel()
 	var a TGSRep
-	v := "encode_krb5_tgs_rep"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_rep)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected")
@@ -132,14 +129,13 @@ func TestUnmarshalTGSRep(t *testing.T) {
 func TestUnmarshalTGSRep_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a TGSRep
-	v := "encode_krb5_tgs_rep(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_repOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_TGS_REP, a.MsgType, "MsgType not as expected")
@@ -164,14 +160,13 @@ func TestUnmarshalTGSRep_optionalsNULL(t *testing.T) {
 func TestUnmarshalEncKDCRepPart(t *testing.T) {
 	t.Parallel()
 	var a EncKDCRepPart
-	v := "encode_krb5_enc_kdc_rep_part"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -203,14 +198,13 @@ func TestUnmarshalEncKDCRepPart(t *testing.T) {
 func TestUnmarshalEncKDCRepPart_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a EncKDCRepPart
-	v := "encode_krb5_enc_kdc_rep_part(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_kdc_rep_partOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -256,11 +250,12 @@ func TestUnmarshalASRepDecodeAndDecrypt(t *testing.T) {
 	assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected")
 	//t.Log("Finished testing unecrypted parts of AS REP")
 	ktb, _ := hex.DecodeString(testuser1EType18Keytab)
-	kt, err := keytab.Parse(ktb)
+	kt := keytab.New()
+	err = kt.Unmarshal(ktb)
 	if err != nil {
 		t.Fatalf("keytab parse error: %v\n", err)
 	}
-	cred := credentials.NewCredentials(testUser, testRealm)
+	cred := credentials.New(testUser, testRealm)
 	_, err = asRep.DecryptEncPart(cred.WithKeytab(kt))
 	if err != nil {
 		t.Fatalf("Decryption of AS_REP EncPart failed: %v", err)
@@ -301,7 +296,7 @@ func TestUnmarshalASRepDecodeAndDecrypt_withPassword(t *testing.T) {
 	assert.Equal(t, 1, asRep.Ticket.EncPart.KVNO, "Ticket encrypted part KVNO not as expected")
 	assert.Equal(t, etypeID.AES256_CTS_HMAC_SHA1_96, asRep.EncPart.EType, "Etype of encrypted part not as expected")
 	assert.Equal(t, 0, asRep.EncPart.KVNO, "Encrypted part KVNO not as expected")
-	cred := credentials.NewCredentials(testUser, testRealm)
+	cred := credentials.New(testUser, testRealm)
 	_, err = asRep.DecryptEncPart(cred.WithPassword(testUserPassword))
 	if err != nil {
 		t.Fatalf("Decryption of AS_REP EncPart failed: %v", err)

+ 13 - 13
messages/KDCReq.go

@@ -11,18 +11,18 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 type marshalKDCReq struct {
@@ -339,7 +339,7 @@ func (k *KDCReqBody) Unmarshal(b []byte) error {
 	k.Addresses = m.Addresses
 	k.EncAuthData = m.EncAuthData
 	if len(m.AdditionalTickets.Bytes) > 0 {
-		k.AdditionalTickets, err = UnmarshalTicketsSequence(m.AdditionalTickets)
+		k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets)
 		if err != nil {
 			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets")
 		}

+ 43 - 55
messages/KDCReq_test.go

@@ -7,25 +7,24 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/addrtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/patype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/addrtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/patype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalKDCReqBody(t *testing.T) {
 	t.Parallel()
 	var a KDCReqBody
-	v := "encode_krb5_kdc_req_body"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -67,14 +66,13 @@ func TestUnmarshalKDCReqBody(t *testing.T) {
 func TestUnmarshalKDCReqBody_optionalsNULLexceptsecond_ticket(t *testing.T) {
 	t.Parallel()
 	var a KDCReqBody
-	v := "encode_krb5_kdc_req_body(optionalsNULLexceptsecond_ticket)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptsecond_ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -102,14 +100,13 @@ func TestUnmarshalKDCReqBody_optionalsNULLexceptsecond_ticket(t *testing.T) {
 func TestUnmarshalKDCReqBody_optionalsNULLexceptserver(t *testing.T) {
 	t.Parallel()
 	var a KDCReqBody
-	v := "encode_krb5_kdc_req_body(optionalsNULLexceptserver)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_bodyOptionalsNULLexceptserver)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -130,14 +127,13 @@ func TestUnmarshalKDCReqBody_optionalsNULLexceptserver(t *testing.T) {
 func TestUnmarshalASReq(t *testing.T) {
 	t.Parallel()
 	var a ASReq
-	v := "encode_krb5_as_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -186,14 +182,13 @@ func TestUnmarshalASReq(t *testing.T) {
 func TestUnmarshalASReq_optionalsNULLexceptsecond_ticket(t *testing.T) {
 	t.Parallel()
 	var a ASReq
-	v := "encode_krb5_as_req(optionalsNULLexceptsecond_ticket)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptsecond_ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -224,14 +219,13 @@ func TestUnmarshalASReq_optionalsNULLexceptsecond_ticket(t *testing.T) {
 func TestUnmarshalASReq_optionalsNULLexceptserver(t *testing.T) {
 	t.Parallel()
 	var a ASReq
-	v := "encode_krb5_as_req(optionalsNULLexceptserver)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_reqOptionalsNULLexceptserver)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -255,14 +249,13 @@ func TestUnmarshalASReq_optionalsNULLexceptserver(t *testing.T) {
 func TestUnmarshalTGSReq(t *testing.T) {
 	t.Parallel()
 	var a TGSReq
-	v := "encode_krb5_tgs_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -311,14 +304,13 @@ func TestUnmarshalTGSReq(t *testing.T) {
 func TestUnmarshalTGSReq_optionalsNULLexceptsecond_ticket(t *testing.T) {
 	t.Parallel()
 	var a TGSReq
-	v := "encode_krb5_tgs_req(optionalsNULLexceptsecond_ticket)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptsecond_ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -349,14 +341,13 @@ func TestUnmarshalTGSReq_optionalsNULLexceptsecond_ticket(t *testing.T) {
 func TestUnmarshalTGSReq_optionalsNULLexceptserver(t *testing.T) {
 	t.Parallel()
 	var a TGSReq
-	v := "encode_krb5_tgs_req(optionalsNULLexceptserver)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_reqOptionalsNULLexceptserver)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -382,19 +373,18 @@ func TestUnmarshalTGSReq_optionalsNULLexceptserver(t *testing.T) {
 func TestMarshalKDCReqBody(t *testing.T) {
 	t.Parallel()
 	var a KDCReqBody
-	v := "encode_krb5_kdc_req_body"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5kdc_req_body)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	// Marshal and re-unmarshal the result nd then compare
 	mb, err := a.Marshal()
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, b, mb, "Marshal bytes of KDCReqBody not as expected")
 }
@@ -402,14 +392,13 @@ func TestMarshalKDCReqBody(t *testing.T) {
 func TestMarshalASReq(t *testing.T) {
 	t.Parallel()
 	var a ASReq
-	v := "encode_krb5_as_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5as_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err := a.Marshal()
 	if err != nil {
@@ -421,14 +410,13 @@ func TestMarshalASReq(t *testing.T) {
 func TestMarshalTGSReq(t *testing.T) {
 	t.Parallel()
 	var a TGSReq
-	v := "encode_krb5_tgs_req"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5tgs_req)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err := a.Marshal()
 	if err != nil {

+ 7 - 7
messages/KRBCred.go

@@ -5,12 +5,12 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 type marshalKRBCred struct {
@@ -69,7 +69,7 @@ func (k *KRBCred) Unmarshal(b []byte) error {
 	k.MsgType = m.MsgType
 	k.EncPart = m.EncPart
 	if len(m.Tickets.Bytes) > 0 {
-		k.Tickets, err = UnmarshalTicketsSequence(m.Tickets)
+		k.Tickets, err = unmarshalTicketsSequence(m.Tickets)
 		if err != nil {
 			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling tickets within KRB_CRED")
 		}

+ 14 - 17
messages/KRBCred_test.go

@@ -7,24 +7,23 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/addrtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/addrtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalKRBCred(t *testing.T) {
 	t.Parallel()
 	var a KRBCred
-	v := "encode_krb5_cred"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5cred)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_CRED, a.MsgType, "Message type not as expected")
@@ -47,14 +46,13 @@ func TestUnmarshalKRBCred(t *testing.T) {
 func TestUnmarshalEncCredPart(t *testing.T) {
 	t.Parallel()
 	var a EncKrbCredPart
-	v := "encode_krb5_enc_cred_part"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -93,14 +91,13 @@ func TestUnmarshalEncCredPart(t *testing.T) {
 func TestUnmarshalEncCredPart_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a EncKrbCredPart
-	v := "encode_krb5_enc_cred_part(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_cred_partOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)

+ 6 - 6
messages/KRBError.go

@@ -6,12 +6,12 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // KRBError implements RFC 4120 KRB_ERROR: https://tools.ietf.org/html/rfc4120#section-5.9.1.

+ 11 - 13
messages/KRBError_test.go

@@ -6,24 +6,23 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalKRBError(t *testing.T) {
 	t.Parallel()
 	var a KRBError
-	v := "encode_krb5_error"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5error)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -50,14 +49,13 @@ func TestUnmarshalKRBError(t *testing.T) {
 func TestUnmarshalKRBError_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a KRBError
-	v := "encode_krb5_error(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5errorOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)

+ 8 - 8
messages/KRBPriv.go

@@ -5,14 +5,14 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // KRBPriv implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.7.1.

+ 26 - 33
messages/KRBPriv_test.go

@@ -6,24 +6,23 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/addrtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/addrtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 func TestUnmarshalKRBPriv(t *testing.T) {
 	t.Parallel()
 	var a KRBPriv
-	v := "encode_krb5_priv"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5priv)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")
 	assert.Equal(t, msgtype.KRB_PRIV, a.MsgType, "Message type not as expected")
@@ -35,14 +34,13 @@ func TestUnmarshalKRBPriv(t *testing.T) {
 func TestUnmarshalEncPrivPart(t *testing.T) {
 	t.Parallel()
 	var a EncKrbPrivPart
-	v := "encode_krb5_enc_priv_part"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -60,14 +58,13 @@ func TestUnmarshalEncPrivPart(t *testing.T) {
 func TestUnmarshalEncPrivPart_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a EncKrbPrivPart
-	v := "encode_krb5_enc_priv_part(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_partOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	assert.Equal(t, "krb5data", string(a.UserData), "User data not as expected")
 	assert.Equal(t, addrtype.IPv4, a.SAddress.AddrType, "SAddress type not as expected")
@@ -77,14 +74,13 @@ func TestUnmarshalEncPrivPart_optionalsNULL(t *testing.T) {
 func TestMarshalKRBPriv(t *testing.T) {
 	t.Parallel()
 	var a KRBPriv
-	v := "encode_krb5_priv"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5priv)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err := a.Marshal()
 	if err != nil {
@@ -92,14 +88,13 @@ func TestMarshalKRBPriv(t *testing.T) {
 	}
 	assert.Equal(t, b, mb, "marshaled bytes not as expected")
 
-	v = "encode_krb5_enc_priv_part"
-	be, err := hex.DecodeString(testdata.TestVectors[v])
+	be, err := hex.DecodeString(testdata.MarshaledKRB5enc_priv_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.DecryptedEncPart.Unmarshal(be)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err = a.Marshal()
 	if err != nil {
@@ -111,23 +106,21 @@ func TestMarshalKRBPriv(t *testing.T) {
 func TestKRBPriv_EncryptEncPart(t *testing.T) {
 	t.Parallel()
 	var a KRBPriv
-	v := "encode_krb5_priv"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5priv)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
-	v = "encode_krb5_enc_priv_part"
-	b, err = hex.DecodeString(testdata.TestVectors[v])
+	b, err = hex.DecodeString(testdata.MarshaledKRB5enc_priv_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.DecryptedEncPart.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	key := types.EncryptionKey{
 		KeyType:  int32(18),

+ 4 - 4
messages/KRBSafe.go

@@ -5,10 +5,10 @@ import (
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 /*

+ 10 - 12
messages/KRBSafe_test.go

@@ -6,23 +6,22 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/addrtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/addrtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUnmarshalKRBSafe(t *testing.T) {
 	t.Parallel()
 	var a KRBSafe
-	v := "encode_krb5_safe"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5safe)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -44,14 +43,13 @@ func TestUnmarshalKRBSafe(t *testing.T) {
 func TestUnmarshalKRBSafe_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a KRBSafe
-	v := "encode_krb5_safe(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5safeOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 
 	assert.Equal(t, iana.PVNO, a.PVNO, "PVNO not as expected")

+ 53 - 39
messages/Ticket.go

@@ -3,21 +3,22 @@ package messages
 import (
 	"crypto/rand"
 	"fmt"
-	"strings"
+	"log"
 	"time"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/adtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/asnAppTag"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/pac"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/adtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/pac"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 // Reference: https://www.ietf.org/rfc/rfc4120.txt
@@ -54,7 +55,7 @@ type TransitedEncoding struct {
 }
 
 // NewTicket creates a new Ticket instance.
-func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) {
+func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab *keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) {
 	etype, err := crypto.GetEtype(eTypeID)
 	if err != nil {
 		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting etype for new ticket")
@@ -82,7 +83,7 @@ func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalNa
 		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncodingError, "error marshalling ticket encpart")
 	}
 	b = asn1tools.AddASNAppTag(b, asnAppTag.EncTicketPart)
-	skey, err := sktab.GetEncryptionKey(sname.NameString, srealm, kvno, eTypeID)
+	skey, err := sktab.GetEncryptionKey(sname, srealm, kvno, eTypeID)
 	if err != nil {
 		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting encryption key for new ticket")
 	}
@@ -121,14 +122,14 @@ func (t *EncTicketPart) Unmarshal(b []byte) error {
 	return err
 }
 
-// UnmarshalTicket returns a ticket from the bytes provided.
-func UnmarshalTicket(b []byte) (t Ticket, err error) {
-	_, err = asn1.UnmarshalWithParams(b, &t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.Ticket))
+// unmarshalTicket returns a ticket from the bytes provided.
+func unmarshalTicket(b []byte) (t Ticket, err error) {
+	err = t.Unmarshal(b)
 	return
 }
 
 // UnmarshalTicketsSequence returns a slice of Tickets from a raw ASN1 value.
-func UnmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) {
+func unmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) {
 	//This is a workaround to a asn1 decoding issue in golang - https://github.com/golang/go/issues/17321. It's not pretty I'm afraid
 	//We pull out raw values from the larger raw value (that is actually the data of the sequence of raw values) and track our position moving along the data.
 	b := in.Bytes
@@ -141,7 +142,7 @@ func UnmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) {
 		if err != nil {
 			return nil, fmt.Errorf("unmarshaling sequence of tickets failed getting length of ticket: %v", err)
 		}
-		t, err := UnmarshalTicket(b[p:])
+		t, err := unmarshalTicket(b[p:])
 		if err != nil {
 			return nil, fmt.Errorf("unmarshaling sequence of tickets failed: %v", err)
 		}
@@ -186,22 +187,21 @@ func MarshalTicketSequence(tkts []Ticket) (asn1.RawValue, error) {
 }
 
 // DecryptEncPart decrypts the encrypted part of the ticket.
-func (t *Ticket) DecryptEncPart(keytab keytab.Keytab, ktprinc string) error {
-	var upn types.PrincipalName
-	realm := t.Realm
-	if ktprinc != "" {
-		var r string
-		upn, r = types.ParseSPNString(ktprinc)
-		if r != "" {
-			realm = r
-		}
-	} else {
-		upn = t.SName
+// The sname argument can be used to specify which service principal's key should be used to decrypt the ticket.
+// If nil is passed as the sname then the service principal specified within the ticket it used.
+func (t *Ticket) DecryptEncPart(keytab *keytab.Keytab, sname *types.PrincipalName) error {
+	if sname == nil {
+		sname = &t.SName
 	}
-	key, err := keytab.GetEncryptionKey(upn.NameString, realm, t.EncPart.KVNO, t.EncPart.EType)
+	key, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
 	if err != nil {
 		return NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
 	}
+	return t.Decrypt(key)
+}
+
+// Decrypt decrypts the encrypted part of the ticket using the key provided.
+func (t *Ticket) Decrypt(key types.EncryptionKey) error {
 	b, err := crypto.DecryptEncPart(t.EncPart, key, keyusage.KDC_REP_TICKET)
 	if err != nil {
 		return fmt.Errorf("error decrypting Ticket EncPart: %v", err)
@@ -216,13 +216,14 @@ func (t *Ticket) DecryptEncPart(keytab keytab.Keytab, ktprinc string) error {
 }
 
 // GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed.
-func (t *Ticket) GetPACType(keytab keytab.Keytab, ktprinc string) (bool, pac.PACType, error) {
+func (t *Ticket) GetPACType(keytab *keytab.Keytab, sname *types.PrincipalName, l *log.Logger) (bool, pac.PACType, error) {
 	var isPAC bool
 	for _, ad := range t.DecryptedEncPart.AuthorizationData {
 		if ad.ADType == adtype.ADIfRelevant {
 			var ad2 types.AuthorizationData
 			err := ad2.Unmarshal(ad.ADData)
 			if err != nil {
+				l.Printf("PAC authorization data could not be unmarshaled: %v", err)
 				continue
 			}
 			if ad2[0].ADType == adtype.ADWin2KPAC {
@@ -232,20 +233,33 @@ func (t *Ticket) GetPACType(keytab keytab.Keytab, ktprinc string) (bool, pac.PAC
 				if err != nil {
 					return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err)
 				}
-				var upn []string
-				if ktprinc != "" {
-					upn = strings.Split(ktprinc, "/")
-				} else {
-					upn = t.SName.NameString
+				if sname == nil {
+					sname = &t.SName
 				}
-				key, err := keytab.GetEncryptionKey(upn, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
+				key, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
 				if err != nil {
 					return isPAC, p, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
 				}
-				err = p.ProcessPACInfoBuffers(key)
+				err = p.ProcessPACInfoBuffers(key, l)
 				return isPAC, p, err
 			}
 		}
 	}
 	return isPAC, pac.PACType{}, nil
 }
+
+// Valid checks it the ticket is currently valid. Max duration passed endtime passed in as argument.
+func (t *Ticket) Valid(d time.Duration) (bool, error) {
+	// Check for future tickets or invalid tickets
+	time := time.Now().UTC()
+	if t.DecryptedEncPart.StartTime.Sub(time) > d || types.IsFlagSet(&t.DecryptedEncPart.Flags, flags.Invalid) {
+		return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "service ticket provided is not yet valid")
+	}
+
+	// Check for expired ticket
+	if time.Sub(t.DecryptedEncPart.EndTime) > d {
+		return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "service ticket provided has expired")
+	}
+
+	return true, nil
+}

+ 32 - 30
messages/Ticket_test.go

@@ -1,33 +1,34 @@
 package messages
 
 import (
+	"bytes"
 	"encoding/hex"
 	"fmt"
+	"log"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/addrtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/adtype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/trtype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/iana"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/addrtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/adtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/trtype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 func TestUnmarshalTicket(t *testing.T) {
 	t.Parallel()
 	var a Ticket
-	v := "encode_krb5_ticket"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 
 	assert.Equal(t, iana.PVNO, a.TktVNO, "Ticket version number not as expected")
@@ -43,14 +44,13 @@ func TestUnmarshalTicket(t *testing.T) {
 func TestUnmarshalEncTicketPart(t *testing.T) {
 	t.Parallel()
 	var a EncTicketPart
-	v := "encode_krb5_enc_tkt_part"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_part)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -81,14 +81,13 @@ func TestUnmarshalEncTicketPart(t *testing.T) {
 func TestUnmarshalEncTicketPart_optionalsNULL(t *testing.T) {
 	t.Parallel()
 	var a EncTicketPart
-	v := "encode_krb5_enc_tkt_part(optionalsNULL)"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5enc_tkt_partOptionalsNULL)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	//Parse the test time value into a time.Time type
 	tt, _ := time.Parse(testdata.TEST_TIME_FORMAT, testdata.TEST_TIME)
@@ -108,14 +107,13 @@ func TestUnmarshalEncTicketPart_optionalsNULL(t *testing.T) {
 func TestMarshalTicket(t *testing.T) {
 	t.Parallel()
 	var a Ticket
-	v := "encode_krb5_ticket"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = a.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 	mb, err := a.Marshal()
 	if err != nil {
@@ -126,10 +124,9 @@ func TestMarshalTicket(t *testing.T) {
 
 func TestAuthorizationData_GetPACType_GOKRB5TestData(t *testing.T) {
 	t.Parallel()
-	v := "PAC_AuthorizationData_GOKRB5"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_AuthorizationData_GOKRB5)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	var a types.AuthorizationData
 	err = a.Unmarshal(b)
@@ -147,10 +144,15 @@ func TestAuthorizationData_GetPACType_GOKRB5TestData(t *testing.T) {
 		},
 	}
 	b, _ = hex.DecodeString(testdata.SYSHTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	isPAC, pac, err := tkt.GetPACType(kt, "sysHTTP")
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	sname := types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: []string{"sysHTTP"}}
+	w := bytes.NewBufferString("")
+	l := log.New(w, "", 0)
+	isPAC, pac, err := tkt.GetPACType(kt, &sname, l)
 	if err != nil {
-		t.Fatalf("Error getting PAC Type: %v\n", err)
+		t.Log(w.String())
+		t.Errorf("error getting PAC: %v", err)
 	}
 	assert.True(t, isPAC, "PAC should be present")
 	assert.Equal(t, 5, len(pac.Buffers), "Number of buffers not as expected")

+ 7 - 7
pac/client_claims_test.go

@@ -5,7 +5,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 	"gopkg.in/jcmturner/rpc.v1/mstypes"
 )
 
@@ -19,7 +19,7 @@ const (
 
 func TestPAC_ClientClaimsInfoStr_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoStr"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoStr)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -40,7 +40,7 @@ func TestPAC_ClientClaimsInfoStr_Unmarshal(t *testing.T) {
 
 func TestPAC_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMultiUint"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiUint)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -61,7 +61,7 @@ func TestPAC_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) {
 
 func TestPAC_ClientClaimsInt_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoInt"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoInt)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -82,7 +82,7 @@ func TestPAC_ClientClaimsInt_Unmarshal(t *testing.T) {
 
 func TestPAC_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMultiStr"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMultiStr)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -104,7 +104,7 @@ func TestPAC_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) {
 func TestPAC_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) {
 	// Has an int and a str claim type
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMulti"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfoMulti)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -130,7 +130,7 @@ func TestPAC_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) {
 // Compressed claims not yet supported.
 //func TestPAC_ClientClaimsInfo_Unmarshal_UnsupportedCompression(t *testing.T) {
 //	t.Parallel()
-//	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfo_XPRESS_HUFF"])
+//	b, err := hex.DecodeString(testdata.MarshaledPAC_ClientClaimsInfo_XPRESS_HUFF)
 //	if err != nil {
 //		t.Fatal("Could not decode test data hex string")
 //	}

+ 2 - 2
pac/client_info_test.go

@@ -6,12 +6,12 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestPAC_ClientInfo_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_Client_Info"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_Client_Info)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}

+ 3 - 3
pac/credentials_info.go

@@ -5,9 +5,9 @@ import (
 	"errors"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 	"gopkg.in/jcmturner/rpc.v1/mstypes"
 	"gopkg.in/jcmturner/rpc.v1/ndr"
 )

+ 4 - 4
pac/kerb_validation_info_test.go

@@ -6,13 +6,13 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 	"gopkg.in/jcmturner/rpc.v1/mstypes"
 )
 
 func TestKerbValidationInfo_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_Kerb_Validation_Info_MS"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_MS)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -113,7 +113,7 @@ func TestKerbValidationInfo_Unmarshal(t *testing.T) {
 	assert.Equal(t, uint8(0), k.ResourceGroupDomainSID.SubAuthorityCount, "ResourceGroupDomainSID not as expected")
 	assert.Equal(t, 0, len(k.ResourceGroupIDs), "ResourceGroupIDs not as expected")
 
-	b, err = hex.DecodeString(testdata.TestVectors["PAC_Kerb_Validation_Info"])
+	b, err = hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -186,7 +186,7 @@ func TestKerbValidationInfo_Unmarshal(t *testing.T) {
 }
 
 func TestKerbValidationInfo_Unmarshal_DomainTrust(t *testing.T) {
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_Kerb_Validation_Info_Trust"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_Kerb_Validation_Info_Trust)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}

+ 17 - 75
pac/pac_type.go

@@ -4,10 +4,11 @@ import (
 	"bytes"
 	"errors"
 	"fmt"
+	"log"
 
-	"gopkg.in/jcmturner/gokrb5.v6/crypto"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/crypto"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 	"gopkg.in/jcmturner/rpc.v1/mstypes"
 )
 
@@ -84,73 +85,9 @@ func (pac *PACType) Unmarshal(b []byte) (err error) {
 	return nil
 }
 
-// PACInfoMandatoryBuffers processes the mandatory PAC Info Buffers that must be present in the PAC.
-func (pac *PACType) PACInfoMandatoryBuffers(key types.EncryptionKey) error {
-	for _, buf := range pac.Buffers {
-		p := make([]byte, buf.CBBufferSize, buf.CBBufferSize)
-		copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)])
-		switch buf.ULType {
-		case infoTypeKerbValidationInfo:
-			if pac.KerbValidationInfo != nil {
-				//Must ignore subsequent buffers of this type
-				continue
-			}
-			var k KerbValidationInfo
-			err := k.Unmarshal(p)
-			if err != nil {
-				return fmt.Errorf("error processing KerbValidationInfo: %v", err)
-			}
-			pac.KerbValidationInfo = &k
-		case infoTypePACServerSignatureData:
-			if pac.ServerChecksum != nil {
-				//Must ignore subsequent buffers of this type
-				continue
-			}
-			var k SignatureData
-			zb, err := k.Unmarshal(p)
-			copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb)
-			if err != nil {
-				return fmt.Errorf("error processing ServerChecksum: %v", err)
-			}
-			pac.ServerChecksum = &k
-		case infoTypePACKDCSignatureData:
-			if pac.KDCChecksum != nil {
-				//Must ignore subsequent buffers of this type
-				continue
-			}
-			var k SignatureData
-			zb, err := k.Unmarshal(p)
-			copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb)
-			if err != nil {
-				return fmt.Errorf("error processing KDCChecksum: %v", err)
-			}
-			pac.KDCChecksum = &k
-		case infoTypePACClientInfo:
-			if pac.ClientInfo != nil {
-				//Must ignore subsequent buffers of this type
-				continue
-			}
-			var k ClientInfo
-			err := k.Unmarshal(p)
-			if err != nil {
-				return fmt.Errorf("error processing ClientInfo: %v", err)
-			}
-			pac.ClientInfo = &k
-		default:
-			continue
-		}
-	}
-
-	if ok, err := pac.validate(key); !ok {
-		return err
-	}
-
-	return nil
-}
-
 // ProcessPACInfoBuffers processes the PAC Info Buffers.
 // https://msdn.microsoft.com/en-us/library/cc237954.aspx
-func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
+func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey, l *log.Logger) error {
 	for _, buf := range pac.Buffers {
 		p := make([]byte, buf.CBBufferSize, buf.CBBufferSize)
 		copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)])
@@ -224,7 +161,8 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			var k S4UDelegationInfo
 			err := k.Unmarshal(p)
 			if err != nil {
-				return fmt.Errorf("error processing S4U_DelegationInfo: %v", err)
+				l.Printf("could not process S4U_DelegationInfo: %v", err)
+				continue
 			}
 			pac.S4UDelegationInfo = &k
 		case infoTypeUPNDNSInfo:
@@ -235,7 +173,8 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			var k UPNDNSInfo
 			err := k.Unmarshal(p)
 			if err != nil {
-				return fmt.Errorf("error processing UPN_DNSInfo: %v", err)
+				l.Printf("could not process UPN_DNSInfo: %v", err)
+				continue
 			}
 			pac.UPNDNSInfo = &k
 		case infoTypePACClientClaimsInfo:
@@ -246,7 +185,8 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			var k ClientClaimsInfo
 			err := k.Unmarshal(p)
 			if err != nil {
-				return fmt.Errorf("error processing ClientClaimsInfo: %v", err)
+				l.Printf("could not process ClientClaimsInfo: %v", err)
+				continue
 			}
 			pac.ClientClaimsInfo = &k
 		case infoTypePACDeviceInfo:
@@ -257,7 +197,8 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			var k DeviceInfo
 			err := k.Unmarshal(p)
 			if err != nil {
-				return fmt.Errorf("error processing DeviceInfo: %v", err)
+				l.Printf("could not process DeviceInfo: %v", err)
+				continue
 			}
 			pac.DeviceInfo = &k
 		case infoTypePACDeviceClaimsInfo:
@@ -268,20 +209,21 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			var k DeviceClaimsInfo
 			err := k.Unmarshal(p)
 			if err != nil {
-				return fmt.Errorf("error processing DeviceClaimsInfo: %v", err)
+				l.Printf("could not process DeviceClaimsInfo: %v", err)
+				continue
 			}
 			pac.DeviceClaimsInfo = &k
 		}
 	}
 
-	if ok, err := pac.validate(key); !ok {
+	if ok, err := pac.verify(key); !ok {
 		return err
 	}
 
 	return nil
 }
 
-func (pac *PACType) validate(key types.EncryptionKey) (bool, error) {
+func (pac *PACType) verify(key types.EncryptionKey) (bool, error) {
 	if pac.KerbValidationInfo == nil {
 		return false, errors.New("PAC Info Buffers does not contain a KerbValidationInfo")
 	}

+ 16 - 10
pac/pac_type_test.go

@@ -1,21 +1,23 @@
 package pac
 
 import (
+	"bytes"
 	"encoding/hex"
 	"fmt"
+	"log"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
-func TestPACTypeValidate(t *testing.T) {
+func TestPACTypeVerify(t *testing.T) {
 	t.Parallel()
-	v := "PAC_AD_WIN2K_PAC"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_AD_WIN2K_PAC)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	var pac PACType
 	err = pac.Unmarshal(b)
@@ -24,12 +26,16 @@ func TestPACTypeValidate(t *testing.T) {
 	}
 
 	b, _ = hex.DecodeString(testdata.SYSHTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
-	key, err := kt.GetEncryptionKey([]string{"sysHTTP"}, "TEST.GOKRB5", 2, 18)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	pn, _ := types.ParseSPNString("sysHTTP")
+	key, err := kt.GetEncryptionKey(pn, "TEST.GOKRB5", 2, 18)
 	if err != nil {
 		t.Fatalf("Error getting key: %v", err)
 	}
-	err = pac.ProcessPACInfoBuffers(key)
+	w := bytes.NewBufferString("")
+	l := log.New(w, "", 0)
+	err = pac.ProcessPACInfoBuffers(key, l)
 	if err != nil {
 		t.Fatalf("Processing reference pac error: %v", err)
 	}
@@ -56,7 +62,7 @@ func TestPACTypeValidate(t *testing.T) {
 		{pacInvalidClientInfo},
 	}
 	for i, s := range pacs {
-		v, _ := s.pac.validate(key)
+		v, _ := s.pac.verify(key)
 		assert.False(t, v, fmt.Sprintf("Validation should have failed for test %v", i))
 	}
 

+ 1 - 1
pac/signature_data.go

@@ -3,7 +3,7 @@ package pac
 import (
 	"bytes"
 
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
 	"gopkg.in/jcmturner/rpc.v1/mstypes"
 )
 

+ 4 - 4
pac/signature_data_test.go

@@ -5,13 +5,13 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestPAC_SignatureData_Unmarshal_Server_Signature(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_Server_Signature"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_Server_Signature)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}
@@ -30,7 +30,7 @@ func TestPAC_SignatureData_Unmarshal_Server_Signature(t *testing.T) {
 
 func TestPAC_SignatureData_Unmarshal_KDC_Signature(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_KDC_Signature"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_KDC_Signature)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}

+ 2 - 2
pac/upn_dns_info_test.go

@@ -5,12 +5,12 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
 func TestUPN_DNSInfo_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(testdata.TestVectors["PAC_UPN_DNS_Info"])
+	b, err := hex.DecodeString(testdata.MarshaledPAC_UPN_DNS_Info)
 	if err != nil {
 		t.Fatal("Could not decode test data hex string")
 	}

+ 20 - 66
service/APExchange.go

@@ -1,89 +1,43 @@
 package service
 
 import (
-	"fmt"
 	"time"
 
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
 )
 
 // ValidateAPREQ 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, sa SPNEGOAuthenticator) (bool, credentials.Credentials, error) {
-	var creds credentials.Credentials
-	err := APReq.Ticket.DecryptEncPart(sa.Config.Keytab, sa.Config.ServicePrincipal)
-	if err != nil {
-		return false, creds, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
-	}
-	a, err := APReq.DecryptAuthenticator(APReq.Ticket.DecryptedEncPart.Key)
-	if err != nil {
-		return false, creds, krberror.Errorf(err, krberror.DecryptingError, "error extracting authenticator")
-	}
-	// 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(sa.ClientAddr)
-		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
-		}
-	} else if sa.Config.RequireHostAddr {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "ticket does not contain HostAddress values required")
-		return false, creds, err
-	}
+func VerifyAPREQ(APReq messages.APReq, s *Settings) (bool, *credentials.Credentials, error) {
+	var creds *credentials.Credentials
 
-	// 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))
+	ok, err := APReq.Verify(s.Keytab, s.MaxClockSkew(), s.ClientAddress())
+	if err != nil || !ok {
 		return false, creds, err
 	}
 
-	// Check for replay
-	rc := GetReplayCache(d)
-	if rc.IsReplay(APReq.Ticket.SName, a) {
-		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "Replay detected")
-		return false, creds, err
+	if s.RequireHostAddr() && len(APReq.Ticket.DecryptedEncPart.CAddr) < 1 {
+		return false, creds,
+			messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "ticket does not contain HostAddress values required")
 	}
 
-	// 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 replay
+	rc := GetReplayCache(s.MaxClockSkew())
+	if rc.IsReplay(APReq.Ticket.SName, APReq.Authenticator) {
+		return false, creds,
+			messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_REPEAT, "replay detected")
 	}
 
-	// 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)
-	creds.SetAuthTime(t)
+	c := credentials.NewFromPrincipalName(APReq.Authenticator.CName, APReq.Authenticator.CRealm)
+	creds = c
+	creds.SetAuthTime(time.Now().UTC())
 	creds.SetAuthenticated(true)
 	creds.SetValidUntil(APReq.Ticket.DecryptedEncPart.EndTime)
 
 	//PAC decoding
-	if !sa.Config.DisablePACDecoding {
-		isPAC, pac, err := APReq.Ticket.GetPACType(sa.Config.Keytab, sa.Config.ServicePrincipal)
+	if !s.disablePACDecoding {
+		isPAC, pac, err := APReq.Ticket.GetPACType(s.Keytab, s.KeytabPrincipal(), s.Logger())
 		if isPAC && err != nil {
 			return false, creds, err
 		}

+ 65 - 58
service/APExchange_test.go

@@ -6,19 +6,19 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/errorcode"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
-func TestValidateAPREQ(t *testing.T) {
+func TestVerifyAPREQ(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -26,9 +26,10 @@ func TestValidateAPREQ(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -51,15 +52,15 @@ func TestValidateAPREQ(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if !ok || err != nil {
 		t.Fatalf("Validation of AP_REQ failed when it should not have: %v", err)
 	}
 }
 
-func TestValidateAPREQ_KRB_AP_ERR_BADMATCH(t *testing.T) {
+func TestVerifyAPREQ_KRB_AP_ERR_BADMATCH(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -67,9 +68,10 @@ func TestValidateAPREQ_KRB_AP_ERR_BADMATCH(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -96,9 +98,9 @@ func TestValidateAPREQ_KRB_AP_ERR_BADMATCH(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -109,7 +111,7 @@ func TestValidateAPREQ_KRB_AP_ERR_BADMATCH(t *testing.T) {
 	}
 }
 
-func TestValidateAPREQ_LargeClockSkew(t *testing.T) {
+func TestVerifyAPREQ_LargeClockSkew(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -117,9 +119,10 @@ func TestValidateAPREQ_LargeClockSkew(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -144,9 +147,9 @@ func TestValidateAPREQ_LargeClockSkew(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -157,7 +160,7 @@ func TestValidateAPREQ_LargeClockSkew(t *testing.T) {
 	}
 }
 
-func TestValidateAPREQ_Replay(t *testing.T) {
+func TestVerifyAPREQ_Replay(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -165,9 +168,10 @@ func TestValidateAPREQ_Replay(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -190,14 +194,14 @@ func TestValidateAPREQ_Replay(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if !ok || err != nil {
 		t.Fatalf("Validation of AP_REQ failed when it should not have: %v", err)
 	}
 	// Replay
-	ok, _, err = ValidateAPREQ(APReq, c)
+	ok, _, err = VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -205,7 +209,7 @@ func TestValidateAPREQ_Replay(t *testing.T) {
 	assert.Equal(t, errorcode.KRB_AP_ERR_REPEAT, err.(messages.KRBError).ErrorCode, "Error code not as expected")
 }
 
-func TestValidateAPREQ_FutureTicket(t *testing.T) {
+func TestVerifyAPREQ_FutureTicket(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -213,9 +217,10 @@ func TestValidateAPREQ_FutureTicket(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -239,9 +244,9 @@ func TestValidateAPREQ_FutureTicket(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -252,7 +257,7 @@ func TestValidateAPREQ_FutureTicket(t *testing.T) {
 	}
 }
 
-func TestValidateAPREQ_InvalidTicket(t *testing.T) {
+func TestVerifyAPREQ_InvalidTicket(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -260,11 +265,12 @@ func TestValidateAPREQ_InvalidTicket(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
 	f := types.NewKrbFlags()
 	types.SetFlag(&f, flags.Invalid)
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		f,
 		kt,
@@ -287,9 +293,9 @@ func TestValidateAPREQ_InvalidTicket(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -300,7 +306,7 @@ func TestValidateAPREQ_InvalidTicket(t *testing.T) {
 	}
 }
 
-func TestValidateAPREQ_ExpiredTicket(t *testing.T) {
+func TestVerifyAPREQ_ExpiredTicket(t *testing.T) {
 	t.Parallel()
 	cl := getClient()
 	sname := types.PrincipalName{
@@ -308,9 +314,10 @@ func TestValidateAPREQ_ExpiredTicket(t *testing.T) {
 		NameString: []string{"HTTP", "host.test.gokrb5"},
 	}
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	st := time.Now().UTC()
-	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
+	tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName(), cl.Credentials.Domain(),
 		sname, "TEST.GOKRB5",
 		types.NewKrbFlags(),
 		kt,
@@ -334,9 +341,9 @@ func TestValidateAPREQ_ExpiredTicket(t *testing.T) {
 		t.Fatalf("Error getting test AP_REQ: %v", err)
 	}
 
-	c := NewSPNEGOAuthenticator(kt)
-	c.ClientAddr = "127.0.0.1"
-	ok, _, err := ValidateAPREQ(APReq, c)
+	h, _ := types.GetHostAddress("127.0.0.1:1234")
+	s := NewSettings(kt, ClientAddress(h))
+	ok, _, err := VerifyAPREQ(APReq, s)
 	if ok || err == nil {
 		t.Fatal("Validation of AP_REQ passed when it should not have")
 	}
@@ -348,7 +355,7 @@ func TestValidateAPREQ_ExpiredTicket(t *testing.T) {
 }
 
 func newTestAuthenticator(creds credentials.Credentials) types.Authenticator {
-	auth, _ := types.NewAuthenticator(creds.Realm, creds.CName)
+	auth, _ := types.NewAuthenticator(creds.Domain(), creds.CName())
 	auth.GenerateSeqNumberAndSubKey(18, 32)
 	//auth.Cksum = types.Checksum{
 	//	CksumType: chksumtype.GSSAPI,
@@ -357,11 +364,11 @@ func newTestAuthenticator(creds credentials.Credentials) types.Authenticator {
 	return auth
 }
 
-func getClient() client.Client {
+func getClient() *client.Client {
 	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
-	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
-	cl.WithConfig(c)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
 	return cl
 }

+ 17 - 110
service/authenticator.go

@@ -2,125 +2,33 @@ package service
 
 import (
 	"encoding/base64"
-	"errors"
 	"fmt"
 	"strings"
 	"time"
 
 	goidentity "gopkg.in/jcmturner/goidentity.v3"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/config"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/gssapi"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
 )
 
-// SPNEGOAuthenticator implements gopkg.in/jcmturner/goidentity.v3.Authenticator interface
-type SPNEGOAuthenticator struct {
-	SPNEGOHeaderValue string
-	ClientAddr        string
-	Config            *Config
-}
-
-// Config for service side implementation
-//
-// Keytab (mandatory) - keytab for the service user
-//
-// KeytabPrincipal (optional) - keytab principal override for the service.
-// The service looks for this principal in the keytab to use to decrypt tickets.
-// If "" is passed as KeytabPrincipal then the principal will be automatically derived
-// from the service name (SName) and realm in the ticket the service is trying to decrypt.
-// This is often sufficient if you create the SPN in MIT KDC with: /usr/sbin/kadmin.local -q "add_principal HTTP/<fqdn>"
-// When Active Directory is used for the KDC this may need to be the account name you have set the SPN against
-// (setspn.exe -a "HTTP/<fqdn>" <account name>)
-// If you are unsure run:
-//
-// klist -k <service's keytab file>
-//
-// and use the value from the Principal column for the keytab entry the service should use.
-//
-// RequireHostAddr - require that the kerberos ticket must include client host IP addresses and one must match the client making the request.
-// This is controlled in the client config with the noaddresses option (http://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html).
-//
-// DisablePACDecoding - if set to true decoding of the Microsoft PAC will be disabled.
-type Config struct {
-	Keytab             keytab.Keytab
-	ServicePrincipal   string
-	RequireHostAddr    bool
-	DisablePACDecoding bool
-}
-
-// NewSPNEGOAuthenticator creates a new SPNEGOAuthenticator.
-func NewSPNEGOAuthenticator(kt keytab.Keytab) (a SPNEGOAuthenticator) {
-	a.Config = NewConfig(kt)
-	return
-}
-
-// NewConfig creates a new kerberos service Config.
-func NewConfig(kt keytab.Keytab) *Config {
-	return &Config{Keytab: kt}
-}
-
-// Authenticate performs authentication checks against the negotiation header value provided.
-func (c *Config) Authenticate(neg, addr string) (i goidentity.Identity, ok bool, err error) {
-	a := SPNEGOAuthenticator{
-		SPNEGOHeaderValue: neg,
-		ClientAddr:        addr,
-		Config:            c,
-	}
-	b, err := base64.StdEncoding.DecodeString(a.SPNEGOHeaderValue)
-	if err != nil {
-		err = fmt.Errorf("SPNEGO error in base64 decoding negotiation header: %v", err)
-		return
-	}
-	var spnego gssapi.SPNEGO
-	err = spnego.Unmarshal(b)
-	if !spnego.Init {
-		err = fmt.Errorf("SPNEGO negotiation token is not a NegTokenInit: %v", err)
-		return
-	}
-	if !(spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDKRB5) ||
-		spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDMSLegacyKRB5)) {
-		err = errors.New("SPNEGO OID of MechToken is not of type KRB5")
-		return
+// NewKRB5BasicAuthenticator creates a new NewKRB5BasicAuthenticator
+func NewKRB5BasicAuthenticator(headerVal string, krb5conf *config.Config, serviceSettings *Settings, clientSettings *client.Settings) KRB5BasicAuthenticator {
+	return KRB5BasicAuthenticator{
+		BasicHeaderValue: headerVal,
+		clientConfig:     krb5conf,
+		serviceSettings:  serviceSettings,
+		clientSettings:   clientSettings,
 	}
-	var mt gssapi.MechToken
-	err = mt.Unmarshal(spnego.NegTokenInit.MechToken)
-	if err != nil {
-		err = fmt.Errorf("SPNEGO error unmarshaling MechToken: %v", err)
-		return
-	}
-	if !mt.IsAPReq() {
-		err = errors.New("MechToken does not contain an AP_REQ - KRB_AP_ERR_MSG_TYPE")
-		return
-	}
-
-	ok, creds, err := ValidateAPREQ(mt.APReq, a)
-	if err != nil {
-		err = fmt.Errorf("SPNEGO validation error: %v", err)
-		return
-	}
-	i = &creds
-	return
-}
-
-// Authenticate and retrieve a goidentity.Identity. In this case it is a pointer to a credentials.Credentials
-func (a SPNEGOAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) {
-	return a.Config.Authenticate(a.SPNEGOHeaderValue, a.ClientAddr)
-}
-
-// Mechanism returns the authentication mechanism.
-func (a SPNEGOAuthenticator) Mechanism() string {
-	return "SPNEGO Kerberos"
 }
 
 // KRB5BasicAuthenticator implements gopkg.in/jcmturner/goidentity.v3.Authenticator interface.
 // It takes username and password so can be used for basic authentication.
 type KRB5BasicAuthenticator struct {
-	SPN              string
 	BasicHeaderValue string
-	ServiceConfig    Config
-	ClientConfig     *config.Config
+	serviceSettings  *Settings
+	clientSettings   *client.Settings
+	clientConfig     *config.Config
 	realm            string
 	username         string
 	password         string
@@ -133,27 +41,26 @@ func (a KRB5BasicAuthenticator) Authenticate() (i goidentity.Identity, ok bool,
 		err = fmt.Errorf("could not parse basic authentication header: %v", err)
 		return
 	}
-	cl := client.NewClientWithPassword(a.username, a.realm, a.password)
-	cl.WithConfig(a.ClientConfig)
+	cl := client.NewClientWithPassword(a.username, a.realm, a.password, a.clientConfig)
 	err = cl.Login()
 	if err != nil {
 		// Username and/or password could be wrong
 		err = fmt.Errorf("error with user credentials during login: %v", err)
 		return
 	}
-	tkt, _, err := cl.GetServiceTicket(a.SPN)
+	tkt, _, err := cl.GetServiceTicket(a.serviceSettings.SName())
 	if err != nil {
 		err = fmt.Errorf("could not get service ticket: %v", err)
 		return
 	}
-	err = tkt.DecryptEncPart(a.ServiceConfig.Keytab, a.ServiceConfig.ServicePrincipal)
+	err = tkt.DecryptEncPart(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal())
 	if err != nil {
 		err = fmt.Errorf("could not decrypt service ticket: %v", err)
 		return
 	}
 	cl.Credentials.SetAuthTime(time.Now().UTC())
 	cl.Credentials.SetAuthenticated(true)
-	isPAC, pac, err := tkt.GetPACType(a.ServiceConfig.Keytab, a.ServiceConfig.ServicePrincipal)
+	isPAC, pac, err := tkt.GetPACType(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal(), a.serviceSettings.Logger())
 	if isPAC && err != nil {
 		err = fmt.Errorf("error processing PAC: %v", err)
 		return

+ 1 - 1
service/authenticator_test.go

@@ -10,7 +10,7 @@ import (
 func TestImplementsInterface(t *testing.T) {
 	t.Parallel()
 	//s := new(SPNEGOAuthenticator)
-	var s SPNEGOAuthenticator
+	var s KRB5BasicAuthenticator
 	a := new(goidentity.Authenticator)
 	assert.Implements(t, a, s, "SPNEGOAuthenticator type does not implement the goidentity.Authenticator interface")
 }

+ 3 - 3
service/cache.go

@@ -2,7 +2,7 @@
 package service
 
 import (
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 	"sync"
 	"time"
 )
@@ -52,7 +52,7 @@ type replayCacheEntry struct {
 func (c *Cache) getClientEntries(cname types.PrincipalName) (clientEntries, bool) {
 	c.mux.RLock()
 	defer c.mux.RUnlock()
-	ce, ok := c.entries[cname.GetPrincipalNameString()]
+	ce, ok := c.entries[cname.PrincipalNameString()]
 	return ce, ok
 }
 
@@ -105,7 +105,7 @@ func (c *Cache) AddEntry(sname types.PrincipalName, a types.Authenticator) {
 	} else {
 		c.mux.Lock()
 		defer c.mux.Unlock()
-		c.entries[a.CName.GetPrincipalNameString()] = clientEntries{
+		c.entries[a.CName.PrincipalNameString()] = clientEntries{
 			replayMap: map[time.Time]replayCacheEntry{
 				ct: {
 					presentedTime: time.Now().UTC(),

+ 0 - 83
service/http.go

@@ -1,83 +0,0 @@
-package service
-
-import (
-	"context"
-	"fmt"
-	"log"
-	"net/http"
-	"strings"
-)
-
-// POTENTIAL BREAKING CHANGE notice. Context keys used will change to a name-spaced strings to avoid clashes.
-// If you are using the constants service.CTXKeyAuthenticated and service.CTXKeyCredentials
-// defined below when retrieving data from the request context your code will be unaffected.
-// However if, for example, you are retrieving context like this: r.Context().Value(1) then
-// you will need to update to replace the 1 with service.CTXKeyCredentials.
-type ctxKey int
-
-const (
-	// spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
-	spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
-	// spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
-	spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC"
-	// CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated.
-	CTXKeyAuthenticated ctxKey = 0
-	// CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object.
-	CTXKeyCredentials ctxKey = 1
-	// HTTPHeaderAuthRequest is the header that will hold authn/z information.
-	HTTPHeaderAuthRequest = "Authorization"
-	// HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server.
-	HTTPHeaderAuthResponse = "WWW-Authenticate"
-	// HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO.
-	HTTPHeaderAuthResponseValueKey = "Negotiate"
-	// UnauthorizedMsg is the message returned in the body when authentication fails.
-	UnauthorizedMsg = "Unauthorised.\n"
-)
-
-// SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
-func SPNEGOKRB5Authenticate(f http.Handler, c *Config, l *log.Logger) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2)
-		if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey {
-			w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey)
-			http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
-			return
-		}
-		id, authned, err := c.Authenticate(s[1], r.RemoteAddr)
-		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - %v", r.RemoteAddr, err))
-			return
-		}
-		if authned {
-			ctx := r.Context()
-			ctx = context.WithValue(ctx, CTXKeyCredentials, id)
-			ctx = context.WithValue(ctx, CTXKeyAuthenticated, true)
-			if l != nil {
-				l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
-			}
-			spnegoResponseAcceptCompleted(w)
-			f.ServeHTTP(w, r.WithContext(ctx))
-		} else {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO Kerberos authentication failed: %v", r.RemoteAddr, err))
-			return
-		}
-		return
-	})
-}
-
-// Set the headers for a rejected SPNEGO negotiation and return an unauthorized status code.
-func rejectSPNEGO(w http.ResponseWriter, l *log.Logger, logMsg string) {
-	if l != nil {
-		l.Println(logMsg)
-	}
-	spnegoResponseReject(w)
-}
-
-func spnegoResponseReject(w http.ResponseWriter) {
-	w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject)
-	http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
-}
-
-func spnegoResponseAcceptCompleted(w http.ResponseWriter) {
-	w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted)
-}

+ 136 - 0
service/settings.go

@@ -0,0 +1,136 @@
+package service
+
+import (
+	"log"
+	"time"
+
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
+)
+
+// Settings defines service side configuration settings.
+type Settings struct {
+	Keytab             *keytab.Keytab
+	ktprinc            *types.PrincipalName
+	sname              string
+	requireHostAddr    bool
+	disablePACDecoding bool
+	cAddr              types.HostAddress
+	maxClockSkew       time.Duration
+	logger             *log.Logger
+}
+
+// NewSettings creates a new service Settings.
+func NewSettings(kt *keytab.Keytab, settings ...func(*Settings)) *Settings {
+	s := new(Settings)
+	s.Keytab = kt
+	for _, set := range settings {
+		set(s)
+	}
+	return s
+}
+
+// RequireHostAddr used to configure service side to required host addresses to be specified in Kerberos tickets.
+//
+// s := NewSettings(kt, RequireHostAddr(true))
+func RequireHostAddr(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.requireHostAddr = b
+	}
+}
+
+// RequireHostAddr indicates if the service should require the host address to be included in the ticket.
+func (s *Settings) RequireHostAddr() bool {
+	return s.requireHostAddr
+}
+
+// DecodePAC used to configure service side to enable/disable PAC decoding if the PAC is present.
+// Defaults to enabled if not specified.
+//
+// s := NewSettings(kt, DecodePAC(false))
+func DecodePAC(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.disablePACDecoding = !b
+	}
+}
+
+// DecodePAC indicates whether the service should decode any PAC information present in the ticket.
+func (s *Settings) DecodePAC() bool {
+	return !s.disablePACDecoding
+}
+
+// ClientAddress used to configure service side with the clients host address to be used during validation.
+//
+// s := NewSettings(kt, ClientAddress(h))
+func ClientAddress(h types.HostAddress) func(*Settings) {
+	return func(s *Settings) {
+		s.cAddr = h
+	}
+}
+
+// ClientAddress returns the client host address which has been provided to the service.
+func (s *Settings) ClientAddress() types.HostAddress {
+	return s.cAddr
+}
+
+// Logger used to configure service side with a logger.
+//
+// s := NewSettings(kt, Logger(l))
+func Logger(l *log.Logger) func(*Settings) {
+	return func(s *Settings) {
+		s.logger = l
+	}
+}
+
+// Logger returns the logger instances configured for the service. If none is configured nill will be returned.
+func (s *Settings) Logger() *log.Logger {
+	return s.logger
+}
+
+// KeytabPrincipal used to override the principal name used to find the key in the keytab.
+//
+// s := NewSettings(kt, KeytabPrincipal("someaccount"))
+func KeytabPrincipal(p string) func(*Settings) {
+	return func(s *Settings) {
+		pn, _ := types.ParseSPNString(p)
+		s.ktprinc = &pn
+	}
+}
+
+// KeytabPrincipal returns the principal name used to find the key in the keytab if it has been overridden.
+func (s *Settings) KeytabPrincipal() *types.PrincipalName {
+	return s.ktprinc
+}
+
+// MaxClockSkew used to configure service side with the maximum acceptable clock skew
+// between the service and the issue time of kerberos tickets
+//
+// s := NewSettings(kt, MaxClockSkew(d))
+func MaxClockSkew(d time.Duration) func(*Settings) {
+	return func(s *Settings) {
+		s.maxClockSkew = d
+	}
+}
+
+// MaxClockSkew returns the maximum acceptable clock skew between the service and the issue time of kerberos tickets.
+// If none is defined a duration of 5 minutes is returned.
+func (s *Settings) MaxClockSkew() time.Duration {
+	if s.maxClockSkew.Nanoseconds() == 0 {
+		return time.Duration(5) * time.Minute
+	}
+	return s.maxClockSkew
+}
+
+// SName used provide a specific service name to the service settings.
+//
+// s := NewSettings(kt, SName("HTTP/some.service.com"))
+func SName(sname string) func(*Settings) {
+	return func(s *Settings) {
+		s.sname = sname
+	}
+}
+
+// SName returns the specific service name to the service.
+func (s *Settings) SName() string {
+	return s.sname
+}

+ 141 - 0
spnego/http.go

@@ -0,0 +1,141 @@
+package spnego
+
+import (
+	"context"
+	"encoding/base64"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"gopkg.in/jcmturner/goidentity.v3"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/gssapi"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
+)
+
+// Client side functionality //
+
+// SetSPNEGOHeader gets the service ticket and sets it as the SPNEGO authorization header on HTTP request object.
+// To auto generate the SPN from the request object pass a null string "".
+func SetSPNEGOHeader(cl *client.Client, r *http.Request, spn string) error {
+	if spn == "" {
+		spn = "HTTP/" + strings.SplitN(r.Host, ":", 2)[0]
+	}
+	s := SPNEGOClient(cl, spn)
+	err := s.AcquireCred()
+	if err != nil {
+		return fmt.Errorf("could not acquire client credenital: %v", err)
+	}
+	st, err := s.InitSecContext()
+	if err != nil {
+		return fmt.Errorf("could not initialize context: %v", err)
+	}
+	nb, err := st.Marshal()
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO")
+	}
+	hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb)
+	r.Header.Set("Authorization", hs)
+	return nil
+}
+
+// Service side functionality //
+
+type ctxKey string
+
+const (
+	// spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
+	spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
+	// spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
+	spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC"
+	// spnegoNegTokenRespIncompleteKRB5 - Response token specifying incomplete context and KRB5 as the supported mechtype.
+	spnegoNegTokenRespIncompleteKRB5 = "Negotiate oRQwEqADCgEBoQsGCSqGSIb3EgECAg=="
+	// CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated.
+	CTXKeyAuthenticated ctxKey = "github.com/jcmturner/gokrb5/CTXKeyAuthenticated"
+	// CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object.
+	CTXKeyCredentials ctxKey = "github.com/jcmturner/gokrb5/CTXKeyCredentials"
+	// HTTPHeaderAuthRequest is the header that will hold authn/z information.
+	HTTPHeaderAuthRequest = "Authorization"
+	// HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server.
+	HTTPHeaderAuthResponse = "WWW-Authenticate"
+	// HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO.
+	HTTPHeaderAuthResponseValueKey = "Negotiate"
+	// UnauthorizedMsg is the message returned in the body when authentication fails.
+	UnauthorizedMsg = "Unauthorised.\n"
+)
+
+// SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
+func SPNEGOKRB5Authenticate(inner http.Handler, kt *keytab.Keytab, settings ...func(*service.Settings)) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Get the auth header
+		s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2)
+		if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey {
+			// No Authorization header set so return 401 with WWW-Authenticate Negotiate header
+			w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey)
+			http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
+			return
+		}
+
+		// Set up the SPNEGO GSS-API mechanism
+		var spnego *SPNEGO
+		h, err := types.GetHostAddress(r.RemoteAddr)
+		if err == nil {
+			// put in this order so that if the user provides a ClientAddress it will override the one here.
+			o := append([]func(*service.Settings){service.ClientAddress(h)}, settings...)
+			spnego = SPNEGOService(kt, o...)
+		} else {
+			spnego = SPNEGOService(kt, settings...)
+			spnego.Log("%s - SPNEGO could not parse client address: %v", r.RemoteAddr, err)
+		}
+
+		// Decode the header into an SPNEGO context token
+		b, err := base64.StdEncoding.DecodeString(s[1])
+		if err != nil {
+			spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in base64 decoding negotiation header: %v", r.RemoteAddr, err)
+		}
+		var st SPNEGOToken
+		err = st.Unmarshal(b)
+		if err != nil {
+			spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO error in unmarshaling SPNEGO token: %v", r.RemoteAddr, err)
+		}
+
+		// Validate the context token
+		authed, ctx, status := spnego.AcceptSecContext(&st)
+		if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded {
+			spnegoResponseReject(spnego, w, "%s - SPNEGO validation error: %v", r.RemoteAddr, status)
+		}
+		if status.Code == gssapi.StatusContinueNeeded {
+			spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr)
+		}
+		if authed {
+			id := ctx.Value(CTXKeyCredentials).(goidentity.Identity)
+			context.WithValue(r.Context(), CTXKeyCredentials, id)
+			context.WithValue(r.Context(), CTXKeyAuthenticated, ctx.Value(CTXKeyAuthenticated))
+			spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
+			inner.ServeHTTP(w, r.WithContext(ctx))
+		} else {
+			spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr)
+		}
+		return
+	})
+}
+
+func spnegoNegotiateKRB5MechType(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
+	s.Log(format, v...)
+	w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespIncompleteKRB5)
+	http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
+}
+
+func spnegoResponseReject(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
+	s.Log(format, v...)
+	w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject)
+	http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
+}
+
+func spnegoResponseAcceptCompleted(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
+	s.Log(format, v...)
+	w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted)
+}

+ 110 - 147
service/http_test.go → spnego/http_test.go

@@ -1,30 +1,75 @@
-package service
+package spnego
 
 import (
 	"bytes"
+	"crypto/rand"
 	"encoding/hex"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
-	"math/rand"
 	"mime/multipart"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"sync"
 	"testing"
-	"time"
 
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/jcmturner/goidentity.v3"
-	"gopkg.in/jcmturner/gokrb5.v6/client"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/nametype"
-	"gopkg.in/jcmturner/gokrb5.v6/keytab"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/config"
+	"gopkg.in/jcmturner/gokrb5.v7/keytab"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
 )
 
+func TestClient_SetSPNEGOHeader(t *testing.T) {
+	if os.Getenv("INTEGRATION") != "1" {
+		t.Skip("Skipping integration test")
+	}
+	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	addr := os.Getenv("TEST_KDC_ADDR")
+	if addr == "" {
+		addr = testdata.TEST_KDC_ADDR
+	}
+	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
+
+	err := cl.Login()
+	if err != nil {
+		t.Fatalf("error on AS_REQ: %v\n", err)
+	}
+	url := os.Getenv("TEST_HTTP_URL")
+	if url == "" {
+		url = testdata.TEST_HTTP_URL
+	}
+	paths := []string{
+		"/modkerb/index.html",
+		"/modgssapi/index.html",
+	}
+	for _, p := range paths {
+		r, _ := http.NewRequest("GET", url+p, nil)
+		httpResp, err := http.DefaultClient.Do(r)
+		if err != nil {
+			t.Fatalf("%s request error: %v\n", url+p, err)
+		}
+		assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected")
+		err = SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
+		if err != nil {
+			t.Fatalf("error setting client SPNEGO header: %v", err)
+		}
+		httpResp, err = http.DefaultClient.Do(r)
+		if err != nil {
+			t.Fatalf("%s request error: %v\n", url+p, err)
+		}
+		assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
+	}
+}
+
 func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) {
 	s := httpServer()
 	defer s.Close()
@@ -38,37 +83,20 @@ func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) {
 }
 
 func TestService_SPNEGOKRB_ValidUser(t *testing.T) {
+	if os.Getenv("INTEGRATION") != "1" {
+		t.Skip("Skipping integration test")
+	}
+
 	s := httpServer()
 	defer s.Close()
+	r, _ := http.NewRequest("GET", s.URL, nil)
 
 	cl := getClient()
-	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),
-	)
+	err := SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error getting test ticket: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
 
-	r, _ := http.NewRequest("GET", s.URL, nil)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r)
-	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
-	}
 	httpResp, err := http.DefaultClient.Do(r)
 	if err != nil {
 		t.Fatalf("Request error: %v\n", err)
@@ -77,36 +105,18 @@ func TestService_SPNEGOKRB_ValidUser(t *testing.T) {
 }
 
 func TestService_SPNEGOKRB_Replay(t *testing.T) {
+	if os.Getenv("INTEGRATION") != "1" {
+		t.Skip("Skipping integration test")
+	}
+
 	s := httpServer()
 	defer s.Close()
+	r1, _ := http.NewRequest("GET", s.URL, nil)
 
 	cl := getClient()
-	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),
-	)
+	err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error getting test ticket: %v", err)
-	}
-
-	r1, _ := http.NewRequest("GET", s.URL, nil)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r1)
-	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
 
 	// First request with this ticket should be accepted
@@ -124,25 +134,11 @@ func TestService_SPNEGOKRB_Replay(t *testing.T) {
 	assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.")
 
 	// Form a 2nd ticket
-	st = time.Now().UTC()
-	tkt2, sessionKey2, 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 getting test ticket: %v", err)
-	}
 	r2, _ := http.NewRequest("GET", s.URL, nil)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt2, sessionKey2, r2)
+
+	err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
 
 	// First use of 2nd ticket should be accepted
@@ -168,58 +164,25 @@ func TestService_SPNEGOKRB_Replay(t *testing.T) {
 }
 
 func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) {
+	if os.Getenv("INTEGRATION") != "1" {
+		t.Skip("Skipping integration test")
+	}
+
 	s := httpServer()
 	defer s.Close()
+	r1, _ := http.NewRequest("GET", s.URL, nil)
 
 	cl := getClient()
-	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 getting test ticket: %v", err)
-	}
-
-	r1, _ := http.NewRequest("GET", s.URL, nil)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r1)
+	err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
 
-	// Form a 2nd ticket
-	st = time.Now().UTC()
-	tkt2, sessionKey2, 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 getting test ticket: %v", err)
-	}
 	r2, _ := http.NewRequest("GET", s.URL, nil)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt2, sessionKey2, r2)
+
+	err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
 
 	// Concurrent 1st requests should be OK
@@ -241,32 +204,13 @@ func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) {
 }
 
 func TestService_SPNEGOKRB_Upload(t *testing.T) {
+	if os.Getenv("INTEGRATION") != "1" {
+		t.Skip("Skipping integration test")
+	}
+
 	s := httpServer()
 	defer s.Close()
 
-	cl := getClient()
-	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 getting test ticket: %v", err)
-	}
-
 	bodyBuf := &bytes.Buffer{}
 	bodyWriter := multipart.NewWriter(bodyBuf)
 
@@ -285,10 +229,13 @@ func TestService_SPNEGOKRB_Upload(t *testing.T) {
 	bodyWriter.Close()
 
 	r, _ := http.NewRequest("POST", s.URL, bodyBuf)
-	err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r)
+
+	cl := getClient()
+	err = SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
 	if err != nil {
-		t.Fatalf("Error setting client SPNEGO header: %v", err)
+		t.Fatalf("error setting client's SPNEGO header: %v", err)
 	}
+
 	r.Header.Set("Content-Type", bodyWriter.FormDataContentType())
 	httpResp, err := http.DefaultClient.Do(r)
 	if err != nil {
@@ -308,12 +255,12 @@ func httpGet(r *http.Request, wg *sync.WaitGroup) {
 }
 
 func httpServer() *httptest.Server {
-	l := log.New(ioutil.Discard, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
+	l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
-	kt, _ := keytab.Parse(b)
+	kt := keytab.New()
+	kt.Unmarshal(b)
 	th := http.HandlerFunc(testAppHandler)
-	c := NewConfig(kt)
-	s := httptest.NewServer(SPNEGOKRB5Authenticate(th, c, l))
+	s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
 	return s
 }
 
@@ -346,3 +293,19 @@ func testAppHandler(w http.ResponseWriter, r *http.Request) {
 		ctx.Value(CTXKeyCredentials).(goidentity.Identity).Domain())
 	return
 }
+
+func getClient() *client.Client {
+	b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt := keytab.New()
+	kt.Unmarshal(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	c.LibDefaults.NoAddresses = true
+	addr := os.Getenv("TEST_KDC_ADDR")
+	if addr == "" {
+		addr = testdata.TEST_KDC_ADDR
+	}
+	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
+	c.Realms[0].KPasswdServer = []string{addr + ":464"}
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
+	return cl
+}

+ 80 - 46
gssapi/krb5Token.go → spnego/krb5Token.go

@@ -1,51 +1,51 @@
-package gssapi
+package spnego
 
 import (
+	"context"
 	"encoding/binary"
 	"encoding/hex"
 	"errors"
 	"fmt"
 
 	"github.com/jcmturner/gofork/encoding/asn1"
-	"gopkg.in/jcmturner/gokrb5.v6/asn1tools"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v6/krberror"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/asn1tools"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/gssapi"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/krberror"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
-// GSSAPI MechToken IDs and flags.
+// GSSAPI KRB5 MechToken IDs.
 const (
 	TOK_ID_KRB_AP_REQ = "0100"
 	TOK_ID_KRB_AP_REP = "0200"
 	TOK_ID_KRB_ERROR  = "0300"
-
-	GSS_C_DELEG_FLAG    = 1
-	GSS_C_MUTUAL_FLAG   = 2
-	GSS_C_REPLAY_FLAG   = 4
-	GSS_C_SEQUENCE_FLAG = 8
-	GSS_C_CONF_FLAG     = 16
-	GSS_C_INTEG_FLAG    = 32
 )
 
-// MechToken implementation for GSSAPI.
-type MechToken struct {
+// KRB5Token context token implementation for GSSAPI.
+type KRB5Token struct {
 	OID      asn1.ObjectIdentifier
-	TokID    []byte
+	tokID    []byte
 	APReq    messages.APReq
 	APRep    messages.APRep
 	KRBError messages.KRBError
+	settings *service.Settings
+	context  context.Context
 }
 
-// Marshal a MechToken into a slice of bytes.
-func (m *MechToken) Marshal() ([]byte, error) {
+// Marshal a KRB5Token into a slice of bytes.
+func (m *KRB5Token) Marshal() ([]byte, error) {
 	// Create the header
 	b, _ := asn1.Marshal(m.OID)
-	b = append(b, m.TokID...)
+	b = append(b, m.tokID...)
 	var tb []byte
 	var err error
-	switch hex.EncodeToString(m.TokID) {
+	switch hex.EncodeToString(m.tokID) {
 	case TOK_ID_KRB_AP_REQ:
 		tb, err = m.APReq.Marshal()
 		if err != nil {
@@ -63,73 +63,107 @@ func (m *MechToken) Marshal() ([]byte, error) {
 	return asn1tools.AddASNAppTag(b, 0), nil
 }
 
-// Unmarshal a MechToken.
-func (m *MechToken) Unmarshal(b []byte) error {
+// Unmarshal a KRB5Token.
+func (m *KRB5Token) Unmarshal(b []byte) error {
 	var oid asn1.ObjectIdentifier
 	r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
 	if err != nil {
-		return fmt.Errorf("error unmarshalling MechToken OID: %v", err)
+		return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err)
 	}
 	m.OID = oid
-	m.TokID = r[0:2]
-	switch hex.EncodeToString(m.TokID) {
+	m.tokID = r[0:2]
+	switch hex.EncodeToString(m.tokID) {
 	case TOK_ID_KRB_AP_REQ:
 		var a messages.APReq
 		err = a.Unmarshal(r[2:])
 		if err != nil {
-			return fmt.Errorf("error unmarshalling MechToken AP_REQ: %v", err)
+			return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err)
 		}
 		m.APReq = a
 	case TOK_ID_KRB_AP_REP:
 		var a messages.APRep
 		err = a.Unmarshal(r[2:])
 		if err != nil {
-			return fmt.Errorf("error unmarshalling MechToken AP_REP: %v", err)
+			return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err)
 		}
 		m.APRep = a
 	case TOK_ID_KRB_ERROR:
 		var a messages.KRBError
 		err = a.Unmarshal(r[2:])
 		if err != nil {
-			return fmt.Errorf("error unmarshalling MechToken KRBError: %v", err)
+			return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err)
 		}
 		m.KRBError = a
 	}
 	return nil
 }
 
+// Verify a KRB5Token.
+func (m *KRB5Token) Verify() (bool, gssapi.Status) {
+	switch hex.EncodeToString(m.tokID) {
+	case TOK_ID_KRB_AP_REQ:
+		ok, creds, err := service.VerifyAPREQ(m.APReq, m.settings)
+		if err != nil {
+			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
+		}
+		if !ok {
+			return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"}
+		}
+		m.context = context.Background()
+		m.context = context.WithValue(m.context, CTXKeyCredentials, creds)
+		m.context = context.WithValue(m.context, CTXKeyAuthenticated, ok)
+		return true, gssapi.Status{Code: gssapi.StatusComplete}
+	case TOK_ID_KRB_AP_REP:
+		// Client side
+		// TODO how to verify the AP_REP - not yet implemented
+		return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"}
+	case TOK_ID_KRB_ERROR:
+		if m.KRBError.MsgType != msgtype.KRB_ERROR {
+			return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"}
+		}
+		return true, gssapi.Status{Code: gssapi.StatusUnavailable}
+	}
+	return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"}
+}
+
 // IsAPReq tests if the MechToken contains an AP_REQ.
-func (m *MechToken) IsAPReq() bool {
-	if hex.EncodeToString(m.TokID) == TOK_ID_KRB_AP_REQ {
+func (m *KRB5Token) IsAPReq() bool {
+	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ {
 		return true
 	}
 	return false
 }
 
 // IsAPRep tests if the MechToken contains an AP_REP.
-func (m *MechToken) IsAPRep() bool {
-	if hex.EncodeToString(m.TokID) == TOK_ID_KRB_AP_REP {
+func (m *KRB5Token) IsAPRep() bool {
+	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP {
 		return true
 	}
 	return false
 }
 
 // IsKRBError tests if the MechToken contains an KRB_ERROR.
-func (m *MechToken) IsKRBError() bool {
-	if hex.EncodeToString(m.TokID) == TOK_ID_KRB_ERROR {
+func (m *KRB5Token) IsKRBError() bool {
+	if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR {
 		return true
 	}
 	return false
 }
 
-// NewAPREQMechToken creates new Kerberos AP_REQ MechToken.
-func NewAPREQMechToken(creds credentials.Credentials, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (MechToken, error) {
-	var m MechToken
-	m.OID = MechTypeOIDKRB5
+// Context returns the KRB5 token's context which will contain any verify user identity information.
+func (m *KRB5Token) Context() context.Context {
+	return m.context
+}
+
+// NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ
+func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) {
+	// TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client.
+	var m KRB5Token
+	m.OID = gssapi.OID(gssapi.OIDKRB5)
 	tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
-	m.TokID = tb
+	m.tokID = tb
 
-	auth, err := NewAuthenticator(creds, GSSAPIFlags)
+	auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags)
 	if err != nil {
 		return m, err
 	}
@@ -148,10 +182,10 @@ func NewAPREQMechToken(creds credentials.Credentials, tkt messages.Ticket, sessi
 	return m, nil
 }
 
-// NewAuthenticator creates a new kerberos authenticator for kerberos MechToken
-func NewAuthenticator(creds credentials.Credentials, flags []int) (types.Authenticator, error) {
+// krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken
+func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) {
 	//RFC 4121 Section 4.1.1
-	auth, err := types.NewAuthenticator(creds.Realm, creds.CName)
+	auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
 	if err != nil {
 		return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
 	}
@@ -167,7 +201,7 @@ func newAuthenticatorChksum(flags []int) []byte {
 	a := make([]byte, 24)
 	binary.LittleEndian.PutUint32(a[:4], 16)
 	for _, i := range flags {
-		if i == GSS_C_DELEG_FLAG {
+		if i == gssapi.ContextFlagDeleg {
 			x := make([]byte, 28-len(a))
 			a = append(a, x...)
 		}

+ 52 - 46
gssapi/krb5Token_test.go → spnego/krb5Token_test.go

@@ -1,59 +1,63 @@
-package gssapi
+package spnego
 
 import (
 	"encoding/hex"
 	"math"
 	"testing"
 
+	"github.com/jcmturner/gofork/encoding/asn1"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/jcmturner/gokrb5.v6/credentials"
-	"gopkg.in/jcmturner/gokrb5.v6/iana/msgtype"
-	"gopkg.in/jcmturner/gokrb5.v6/messages"
-	"gopkg.in/jcmturner/gokrb5.v6/testdata"
-	"gopkg.in/jcmturner/gokrb5.v6/types"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/credentials"
+	"gopkg.in/jcmturner/gokrb5.v7/gssapi"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/msgtype"
+	"gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/test/testdata"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
 )
 
 const (
-	MechTokenHex = "6082026306092a864886f71201020201006e8202523082024ea003020105a10302010ea20703050000000000a382015d6182015930820155a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382011830820114a003020112a103020103a28201060482010230621d868c97f30bf401e03bbffcd724bd9d067dce2afc31f71a356449b070cdafcc1ff372d0eb1e7a708b50c0152f3996c45b1ea312a803907fb97192d39f20cdcaea29876190f51de6e2b4a4df0460122ed97f363434e1e120b0e76c172b4424a536987152ac0b73013ab88af4b13a3fcdc63f739039dd46d839709cf5b51bb0ce6cb3af05fab3844caac280929955495235e9d0424f8a1fb9b4bd4f6bba971f40b97e9da60b9dabfcf0b1feebfca02c9a19b327a0004aa8e19192726cf347561fa8ac74afad5d6a264e50cf495b93aac86c77b2bc2d184234f6c2767dbea431485a25687b9044a20b601e968efaefffa1fc5283ff32aa6a53cb6c5cdd2eddcb26a481d73081d4a003020112a103020103a281c70481c4a1b29e420324f7edf9efae39df7bcaaf196a3160cf07e72f52a4ef8a965721b2f3343719c50699046e4fcc18ca26c2bfc7e4a9eddfc9d9cfc57ff2f6bdbbd1fc40ac442195bc669b9a0dbba12563b3e4cac9f4022fc01b8aa2d1ab84815bb078399ff7f4d5f9815eef896a0c7e3c049e6fd9932b97096cdb5861425b9d81753d0743212ded1a0fb55a00bf71a46be5ce5e1c8a5cc327b914347d9efcb6cb31ca363b1850d95c7b6c4c3cc6301615ad907318a0c5379d343610fab17eca9c7dc0a5a60658"
+	KRB5TokenHex = "6082026306092a864886f71201020201006e8202523082024ea003020105a10302010ea20703050000000000a382015d6182015930820155a003020105a10d1b0b544553542e474f4b524235a2233021a003020101a11a30181b04485454501b10686f73742e746573742e676f6b726235a382011830820114a003020112a103020103a28201060482010230621d868c97f30bf401e03bbffcd724bd9d067dce2afc31f71a356449b070cdafcc1ff372d0eb1e7a708b50c0152f3996c45b1ea312a803907fb97192d39f20cdcaea29876190f51de6e2b4a4df0460122ed97f363434e1e120b0e76c172b4424a536987152ac0b73013ab88af4b13a3fcdc63f739039dd46d839709cf5b51bb0ce6cb3af05fab3844caac280929955495235e9d0424f8a1fb9b4bd4f6bba971f40b97e9da60b9dabfcf0b1feebfca02c9a19b327a0004aa8e19192726cf347561fa8ac74afad5d6a264e50cf495b93aac86c77b2bc2d184234f6c2767dbea431485a25687b9044a20b601e968efaefffa1fc5283ff32aa6a53cb6c5cdd2eddcb26a481d73081d4a003020112a103020103a281c70481c4a1b29e420324f7edf9efae39df7bcaaf196a3160cf07e72f52a4ef8a965721b2f3343719c50699046e4fcc18ca26c2bfc7e4a9eddfc9d9cfc57ff2f6bdbbd1fc40ac442195bc669b9a0dbba12563b3e4cac9f4022fc01b8aa2d1ab84815bb078399ff7f4d5f9815eef896a0c7e3c049e6fd9932b97096cdb5861425b9d81753d0743212ded1a0fb55a00bf71a46be5ce5e1c8a5cc327b914347d9efcb6cb31ca363b1850d95c7b6c4c3cc6301615ad907318a0c5379d343610fab17eca9c7dc0a5a60658"
 	AuthChksum   = "100000000000000000000000000000000000000030000000"
 )
 
-func TestMechToken_Unmarshal(t *testing.T) {
+func TestKRB5Token_Unmarshal(t *testing.T) {
 	t.Parallel()
-	b, err := hex.DecodeString(MechTokenHex)
+	b, err := hex.DecodeString(KRB5TokenHex)
 	if err != nil {
-		t.Fatalf("Error decoding MechToken hex: %v", err)
+		t.Fatalf("Error decoding KRB5Token hex: %v", err)
 	}
-	var mt MechToken
+	var mt KRB5Token
 	err = mt.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Error unmarshalling MechToken: %v", err)
+		t.Fatalf("Error unmarshalling KRB5Token: %v", err)
 	}
-	assert.Equal(t, MechTypeOIDKRB5, mt.OID, "MechToken OID not as expected.")
-	assert.Equal(t, []byte{1, 0}, mt.TokID, "TokID not as expected")
-	assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "MechToken AP_REQ does not have the right message type.")
-	assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in MechToken does not indicate no error.")
-	assert.Equal(t, int32(18), mt.APReq.Authenticator.EType, "Authenticator within AP_REQ does not have the etype expected.")
+	assert.Equal(t, gssapi.OID(gssapi.OIDKRB5), mt.OID, "KRB5Token OID not as expected.")
+	assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected")
+	assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.")
+	assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.")
+	assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.")
 }
 
-func TestMechToken_newAuthenticatorChksum(t *testing.T) {
+func TestKRB5Token_newAuthenticatorChksum(t *testing.T) {
 	t.Parallel()
 	b, err := hex.DecodeString(AuthChksum)
 	if err != nil {
-		t.Fatalf("Error decoding MechToken hex: %v", err)
+		t.Fatalf("Error decoding KRB5Token hex: %v", err)
 	}
-	cb := newAuthenticatorChksum([]int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG})
+	cb := newAuthenticatorChksum([]int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf})
 	assert.Equal(t, b, cb, "SPNEGO Authenticator checksum not as expected")
 }
 
 // Test with explicit subkey generation.
-func TestMechToken_newAuthenticatorWithSubkeyGeneration(t *testing.T) {
+func TestKRB5Token_newAuthenticatorWithSubkeyGeneration(t *testing.T) {
 	t.Parallel()
-	creds := credentials.NewCredentials("hftsai", testdata.TEST_REALM)
-	creds.CName.NameString = testdata.TEST_PRINCIPALNAME_NAMESTRING
+	creds := credentials.New("hftsai", testdata.TEST_REALM)
+	creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING})
 	var etypeID int32 = 18
 	keyLen := 32 // etypeID 18 refers to AES256 -> 32 bytes key
-	a, err := NewAuthenticator(creds, []int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG})
+	a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf})
 	if err != nil {
 		t.Fatalf("Error creating authenticator: %v", err)
 	}
@@ -77,11 +81,11 @@ func TestMechToken_newAuthenticatorWithSubkeyGeneration(t *testing.T) {
 }
 
 // Test without subkey generation.
-func TestMechToken_newAuthenticator(t *testing.T) {
+func TestKRB5Token_newAuthenticator(t *testing.T) {
 	t.Parallel()
-	creds := credentials.NewCredentials("hftsai", testdata.TEST_REALM)
-	creds.CName.NameString = testdata.TEST_PRINCIPALNAME_NAMESTRING
-	a, err := NewAuthenticator(creds, []int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG})
+	creds := credentials.New("hftsai", testdata.TEST_REALM)
+	creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING})
+	a, err := krb5TokenAuthenticator(creds, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf})
 	if err != nil {
 		t.Fatalf("Error creating authenticator: %v", err)
 	}
@@ -97,20 +101,22 @@ func TestMechToken_newAuthenticator(t *testing.T) {
 	}))
 }
 
-func TestNewAPREQMechToken_and_Marshal(t *testing.T) {
+func TestNewAPREQKRB5Token_and_Marshal(t *testing.T) {
 	t.Parallel()
-	creds := credentials.NewCredentials("hftsai", testdata.TEST_REALM)
-	creds.CName.NameString = testdata.TEST_PRINCIPALNAME_NAMESTRING
+	creds := credentials.New("hftsai", testdata.TEST_REALM)
+	creds.SetCName(types.PrincipalName{NameType: nametype.KRB_NT_PRINCIPAL, NameString: testdata.TEST_PRINCIPALNAME_NAMESTRING})
+	cl := client.Client{
+		Credentials: creds,
+	}
 
 	var tkt messages.Ticket
-	v := "encode_krb5_ticket"
-	b, err := hex.DecodeString(testdata.TestVectors[v])
+	b, err := hex.DecodeString(testdata.MarshaledKRB5ticket)
 	if err != nil {
-		t.Fatalf("Test vector read error of %s: %v\n", v, err)
+		t.Fatalf("Test vector read error: %v", err)
 	}
 	err = tkt.Unmarshal(b)
 	if err != nil {
-		t.Fatalf("Unmarshal error of %s: %v\n", v, err)
+		t.Fatalf("Unmarshal error: %v", err)
 	}
 
 	key := types.EncryptionKey{
@@ -118,23 +124,23 @@ func TestNewAPREQMechToken_and_Marshal(t *testing.T) {
 		KeyValue: make([]byte, 32),
 	}
 
-	mt, err := NewAPREQMechToken(creds, tkt, key, []int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG}, []int{})
+	mt, err := NewKRB5TokenAPREQ(&cl, tkt, key, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{})
 	if err != nil {
-		t.Fatalf("Error creating MechToken: %v", err)
+		t.Fatalf("Error creating KRB5Token: %v", err)
 	}
 	mb, err := mt.Marshal()
 	if err != nil {
-		t.Fatalf("Error unmarshalling MechToken: %v", err)
+		t.Fatalf("Error unmarshalling KRB5Token: %v", err)
 	}
 	err = mt.Unmarshal(mb)
 	if err != nil {
-		t.Fatalf("Error unmarshalling MechToken: %v", err)
+		t.Fatalf("Error unmarshalling KRB5Token: %v", err)
 	}
-	assert.Equal(t, MechTypeOIDKRB5, mt.OID, "MechToken OID not as expected.")
-	assert.Equal(t, []byte{1, 0}, mt.TokID, "TokID not as expected")
-	assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "MechToken AP_REQ does not have the right message type.")
-	assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in MechToken does not indicate no error.")
-	assert.Equal(t, testdata.TEST_REALM, mt.APReq.Ticket.Realm, "Realm in ticket within the AP_REQ of the MechToken not as expected.")
-	assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, mt.APReq.Ticket.SName.NameString, "SName in ticket within the AP_REQ of the MechToken not as expected.")
-	assert.Equal(t, int32(18), mt.APReq.Authenticator.EType, "Authenticator within AP_REQ does not have the etype expected.")
+	assert.Equal(t, asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}, mt.OID, "KRB5Token OID not as expected.")
+	assert.Equal(t, []byte{1, 0}, mt.tokID, "TokID not as expected")
+	assert.Equal(t, msgtype.KRB_AP_REQ, mt.APReq.MsgType, "KRB5Token AP_REQ does not have the right message type.")
+	assert.Equal(t, int32(0), mt.KRBError.ErrorCode, "KRBError in KRB5Token does not indicate no error.")
+	assert.Equal(t, testdata.TEST_REALM, mt.APReq.Ticket.Realm, "Realm in ticket within the AP_REQ of the KRB5Token not as expected.")
+	assert.Equal(t, testdata.TEST_PRINCIPALNAME_NAMESTRING, mt.APReq.Ticket.SName.NameString, "SName in ticket within the AP_REQ of the KRB5Token not as expected.")
+	assert.Equal(t, int32(18), mt.APReq.EncryptedAuthenticator.EType, "Authenticator within AP_REQ does not have the etype expected.")
 }

+ 328 - 0
spnego/negotiationToken.go

@@ -0,0 +1,328 @@
+package spnego
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"gopkg.in/jcmturner/gokrb5.v7/client"
+	"gopkg.in/jcmturner/gokrb5.v7/gssapi"
+	"gopkg.in/jcmturner/gokrb5.v7/messages"
+	"gopkg.in/jcmturner/gokrb5.v7/service"
+	"gopkg.in/jcmturner/gokrb5.v7/types"
+)
+
+/*
+https://msdn.microsoft.com/en-us/library/ms995330.aspx
+
+NegotiationToken ::= CHOICE {
+  negTokenInit    [0] NegTokenInit,  This is the Negotiation token sent from the client to the server.
+  negTokenResp    [1] NegTokenResp
+}
+
+NegTokenInit ::= SEQUENCE {
+  mechTypes       [0] MechTypeList,
+  reqFlags        [1] ContextFlags  OPTIONAL,
+  -- inherited from RFC 2478 for backward compatibility,
+  -- RECOMMENDED to be left out
+  mechToken       [2] OCTET STRING  OPTIONAL,
+  mechListMIC     [3] OCTET STRING  OPTIONAL,
+  ...
+}
+
+NegTokenResp ::= SEQUENCE {
+  negState       [0] ENUMERATED {
+    accept-completed    (0),
+    accept-incomplete   (1),
+    reject              (2),
+    request-mic         (3)
+  }                                 OPTIONAL,
+  -- REQUIRED in the first reply from the target
+  supportedMech   [1] MechType      OPTIONAL,
+  -- present only in the first reply from the target
+  responseToken   [2] OCTET STRING  OPTIONAL,
+  mechListMIC     [3] OCTET STRING  OPTIONAL,
+  ...
+}
+*/
+
+// Negotiation state values.
+const (
+	NegStateAcceptCompleted  NegState = 0
+	NegStateAcceptIncomplete NegState = 1
+	NegStateReject           NegState = 2
+	NegStateRequestMIC       NegState = 3
+)
+
+// NegState is a type to indicate the SPNEGO negotiation state.
+type NegState int
+
+// NegTokenInit implements Negotiation Token of type Init.
+type NegTokenInit struct {
+	MechTypes      []asn1.ObjectIdentifier
+	ReqFlags       gssapi.ContextFlags
+	MechTokenBytes []byte
+	MechListMIC    []byte
+	mechToken      gssapi.ContextToken
+	settings       *service.Settings
+}
+
+type marshalNegTokenInit struct {
+	MechTypes      []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
+	ReqFlags       gssapi.ContextFlags     `asn1:"explicit,optional,tag:1"`
+	MechTokenBytes []byte                  `asn1:"explicit,optional,omitempty,tag:2"`
+	MechListMIC    []byte                  `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
+}
+
+// NegTokenResp implements Negotiation Token of type Resp/Targ
+type NegTokenResp struct {
+	NegState      asn1.Enumerated
+	SupportedMech asn1.ObjectIdentifier
+	ResponseToken []byte
+	MechListMIC   []byte
+	mechToken     gssapi.ContextToken
+	settings      *service.Settings
+}
+
+type marshalNegTokenResp struct {
+	NegState      asn1.Enumerated       `asn1:"explicit,tag:0"`
+	SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
+	ResponseToken []byte                `asn1:"explicit,optional,omitempty,tag:2"`
+	MechListMIC   []byte                `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
+}
+
+// NegTokenTarg implements Negotiation Token of type Resp/Targ
+type NegTokenTarg NegTokenResp
+
+// Marshal an Init negotiation token
+func (n *NegTokenInit) Marshal() ([]byte, error) {
+	m := marshalNegTokenInit{
+		MechTypes:      n.MechTypes,
+		ReqFlags:       n.ReqFlags,
+		MechTokenBytes: n.MechTokenBytes,
+		MechListMIC:    n.MechListMIC,
+	}
+	b, err := asn1.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	nt := asn1.RawValue{
+		Tag:        0,
+		Class:      2,
+		IsCompound: true,
+		Bytes:      b,
+	}
+	nb, err := asn1.Marshal(nt)
+	if err != nil {
+		return nil, err
+	}
+	return nb, nil
+}
+
+// Unmarshal an Init negotiation token
+func (n *NegTokenInit) Unmarshal(b []byte) error {
+	init, nt, err := UnmarshalNegToken(b)
+	if err != nil {
+		return err
+	}
+	if !init {
+		return errors.New("bytes were not that of a NegTokenInit")
+	}
+	nInit := nt.(NegTokenInit)
+	n.MechTokenBytes = nInit.MechTokenBytes
+	n.MechListMIC = nInit.MechListMIC
+	n.MechTypes = nInit.MechTypes
+	n.ReqFlags = nInit.ReqFlags
+	return nil
+}
+
+// Verify an Init negotiation token
+func (n *NegTokenInit) Verify() (bool, gssapi.Status) {
+	// Check if supported mechanisms are in the MechTypeList
+	for _, m := range n.MechTypes {
+		if m.Equal(gssapi.OID(gssapi.OIDKRB5)) || m.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) {
+			if n.mechToken == nil && n.MechTokenBytes == nil {
+				return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
+			}
+			mt := new(KRB5Token)
+			mt.settings = n.settings
+			if n.mechToken == nil {
+				err := mt.Unmarshal(n.MechTokenBytes)
+				if err != nil {
+					return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
+				}
+				n.mechToken = mt
+			} else {
+				var ok bool
+				mt, ok = n.mechToken.(*KRB5Token)
+				if !ok {
+					return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
+				}
+			}
+			if !mt.OID.Equal(n.MechTypes[0]) {
+				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "OID of MechToken does not match the first in the MechTypeList"}
+			}
+			// Verify the mechtoken
+			return n.mechToken.Verify()
+		}
+	}
+	return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
+}
+
+// Context returns the SPNEGO context which will contain any verify user identity information.
+func (n *NegTokenInit) Context() context.Context {
+	if n.mechToken != nil {
+		mt, ok := n.mechToken.(*KRB5Token)
+		if !ok {
+			return nil
+		}
+		return mt.Context()
+	}
+	return nil
+}
+
+// Marshal a Resp/Targ negotiation token
+func (n *NegTokenResp) Marshal() ([]byte, error) {
+	m := marshalNegTokenResp{
+		NegState:      n.NegState,
+		SupportedMech: n.SupportedMech,
+		ResponseToken: n.ResponseToken,
+		MechListMIC:   n.MechListMIC,
+	}
+	b, err := asn1.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	nt := asn1.RawValue{
+		Tag:        1,
+		Class:      2,
+		IsCompound: true,
+		Bytes:      b,
+	}
+	nb, err := asn1.Marshal(nt)
+	if err != nil {
+		return nil, err
+	}
+	return nb, nil
+}
+
+// Unmarshal a Resp/Targ negotiation token
+func (n *NegTokenResp) Unmarshal(b []byte) error {
+	init, nt, err := UnmarshalNegToken(b)
+	if err != nil {
+		return err
+	}
+	if init {
+		return errors.New("bytes were not that of a NegTokenResp")
+	}
+	nResp := nt.(NegTokenResp)
+	n.MechListMIC = nResp.MechListMIC
+	n.NegState = nResp.NegState
+	n.ResponseToken = nResp.ResponseToken
+	n.SupportedMech = nResp.SupportedMech
+	return nil
+}
+
+// Verify a Resp/Targ negotiation token
+func (n *NegTokenResp) Verify() (bool, gssapi.Status) {
+	if n.SupportedMech.Equal(gssapi.OID(gssapi.OIDKRB5)) || n.SupportedMech.Equal(gssapi.OID(gssapi.OIDMSLegacyKRB5)) {
+		if n.mechToken == nil && n.ResponseToken == nil {
+			return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
+		}
+		var mt *KRB5Token
+		mt.settings = n.settings
+		if n.mechToken == nil {
+			err := mt.Unmarshal(n.ResponseToken)
+			if err != nil {
+				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
+			}
+			n.mechToken = mt
+		} else {
+			var ok bool
+			mt, ok = n.mechToken.(*KRB5Token)
+			if !ok {
+				return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
+			}
+		}
+		// Verify the mechtoken
+		return mt.Verify()
+	}
+	return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
+}
+
+// State returns the negotiation state of the negotiation response.
+func (n *NegTokenResp) State() NegState {
+	return NegState(n.NegState)
+}
+
+// Context returns the SPNEGO context which will contain any verify user identity information.
+func (n *NegTokenResp) Context() context.Context {
+	if n.mechToken != nil {
+		mt, ok := n.mechToken.(*KRB5Token)
+		if !ok {
+			return nil
+		}
+		return mt.Context()
+	}
+	return nil
+}
+
+// UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp.
+//
+// The boolean indicates if the response is a NegTokenInit.
+// If error is nil and the boolean is false the response is a NegTokenResp.
+func UnmarshalNegToken(b []byte) (bool, interface{}, error) {
+	var a asn1.RawValue
+	_, err := asn1.Unmarshal(b, &a)
+	if err != nil {
+		return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err)
+	}
+	switch a.Tag {
+	case 0:
+		var n marshalNegTokenInit
+		_, err = asn1.Unmarshal(a.Bytes, &n)
+		if err != nil {
+			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err)
+		}
+		nt := NegTokenInit{
+			MechTypes:      n.MechTypes,
+			ReqFlags:       n.ReqFlags,
+			MechTokenBytes: n.MechTokenBytes,
+			MechListMIC:    n.MechListMIC,
+		}
+		return true, nt, nil
+	case 1:
+		var n marshalNegTokenResp
+		_, err = asn1.Unmarshal(a.Bytes, &n)
+		if err != nil {
+			return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err)
+		}
+		nt := NegTokenResp{
+			NegState:      n.NegState,
+			SupportedMech: n.SupportedMech,
+			ResponseToken: n.ResponseToken,
+			MechListMIC:   n.MechListMIC,
+		}
+		return false, nt, nil
+	default:
+		return false, nil, errors.New("unknown choice type for NegotiationToken")
+	}
+
+}
+
+// NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5
+func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) {
+	mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{})
+	if err != nil {
+		return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err)
+	}
+	mtb, err := mt.Marshal()
+	if err != nil {
+		return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err)
+	}
+	return NegTokenInit{
+		MechTypes:      []asn1.ObjectIdentifier{gssapi.OID(gssapi.OIDKRB5)},
+		MechTokenBytes: mtb,
+	}, nil
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů