1
0

Update module lib/pq to v1.6.0 (#572)

Update module lib/pq to v1.6.0

Reviewed-on: https://kolaente.dev/vikunja/api/pulls/572
This commit is contained in:
renovate
2020-05-29 17:47:28 +00:00
committed by konrad
parent 5a04f1ecf4
commit 54b18b3c59
161 changed files with 19472 additions and 7 deletions

373
vendor/github.com/jcmturner/gokrb5/v8/spnego/http.go generated vendored Normal file
View File

@ -0,0 +1,373 @@
package spnego
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/jcmturner/goidentity/v6"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/krberror"
"github.com/jcmturner/gokrb5/v8/service"
"github.com/jcmturner/gokrb5/v8/types"
)
// Client side functionality //
// Client will negotiate authentication with a server using SPNEGO.
type Client struct {
*http.Client
krb5Client *client.Client
spn string
reqs []*http.Request
}
type redirectErr struct {
reqTarget *http.Request
}
func (e redirectErr) Error() string {
return fmt.Sprintf("redirect to %v", e.reqTarget.URL)
}
type teeReadCloser struct {
io.Reader
io.Closer
}
// NewClient returns a SPNEGO enabled HTTP client.
// Be careful when passing in the *http.Client if it is beginning reused in multiple calls to this function.
// Ensure reuse of the provided *http.Client is for the same user as a session cookie may have been added to
// http.Client's cookie jar.
// Incorrect reuse of the provided *http.Client could lead to access to the wrong user's session.
func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client {
if httpCl == nil {
httpCl = &http.Client{}
}
// Add a cookie jar if there isn't one
if httpCl.Jar == nil {
httpCl.Jar, _ = cookiejar.New(nil)
}
// Add a CheckRedirect function that will execute any functional already defined and then error with a redirectErr
f := httpCl.CheckRedirect
httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if f != nil {
err := f(req, via)
if err != nil {
return err
}
}
return redirectErr{reqTarget: req}
}
return &Client{
Client: httpCl,
krb5Client: krb5Cl,
spn: spn,
}
}
// Do is the SPNEGO enabled HTTP client's equivalent of the http.Client's Do method.
func (c *Client) Do(req *http.Request) (resp *http.Response, err error) {
var body bytes.Buffer
if req.Body != nil {
// Use a tee reader to capture any body sent in case we have to replay it again
teeR := io.TeeReader(req.Body, &body)
teeRC := teeReadCloser{teeR, req.Body}
req.Body = teeRC
}
resp, err = c.Client.Do(req)
if err != nil {
if ue, ok := err.(*url.Error); ok {
if e, ok := ue.Err.(redirectErr); ok {
// Picked up a redirect
e.reqTarget.Header.Del(HTTPHeaderAuthRequest)
c.reqs = append(c.reqs, e.reqTarget)
if len(c.reqs) >= 10 {
return resp, errors.New("stopped after 10 redirects")
}
if req.Body != nil {
// Refresh the body reader so the body can be sent again
e.reqTarget.Body = ioutil.NopCloser(&body)
}
return c.Do(e.reqTarget)
}
}
return resp, err
}
if respUnauthorizedNegotiate(resp) {
err := SetSPNEGOHeader(c.krb5Client, req, c.spn)
if err != nil {
return resp, err
}
if req.Body != nil {
// Refresh the body reader so the body can be sent again
req.Body = ioutil.NopCloser(&body)
}
return c.Do(req)
}
return resp, err
}
// Get is the SPNEGO enabled HTTP client's equivalent of the http.Client's Get method.
func (c *Client) Get(url string) (resp *http.Response, err error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// Post is the SPNEGO enabled HTTP client's equivalent of the http.Client's Post method.
func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return c.Do(req)
}
// PostForm is the SPNEGO enabled HTTP client's equivalent of the http.Client's PostForm method.
func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) {
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
// Head is the SPNEGO enabled HTTP client's equivalent of the http.Client's Head method.
func (c *Client) Head(url string) (resp *http.Response, err error) {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
func respUnauthorizedNegotiate(resp *http.Response) bool {
if resp.StatusCode == http.StatusUnauthorized {
if resp.Header.Get(HTTPHeaderAuthResponse) == HTTPHeaderAuthResponseValueKey {
return true
}
}
return false
}
// 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 == "" {
h := strings.TrimSuffix(strings.SplitN(r.URL.Host, ":", 2)[0], ".")
name, err := net.LookupCNAME(h)
if err == nil {
// Underlyng canonical name should be used for SPN
h = strings.TrimSuffix(name, ".")
}
spn = "HTTP/" + h
r.Host = h
}
cl.Log("using SPN %s", spn)
s := SPNEGOClient(cl, spn)
err := s.AcquireCred()
if err != nil {
return fmt.Errorf("could not acquire client credential: %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(HTTPHeaderAuthRequest, hs)
return nil
}
// Service side functionality //
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=="
// sessionCredentials is the session value key holding the credentials jcmturner/goidentity/Identity object.
sessionCredentials = "github.com/jcmturner/gokrb5/v8/sessionCredentials"
// ctxCredentials is the SPNEGO context key holding the credentials jcmturner/goidentity/Identity object.
ctxCredentials = "github.com/jcmturner/gokrb5/v8/ctxCredentials"
// 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) {
// 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)
}
// Check if there is a session manager and if there is an already established session for this client
id, err := getSessionCredentials(spnego, r)
if err == nil && id.Authenticated() {
// There is an established session so bypass auth and serve
spnego.Log("%s - SPNEGO request served under session %s", r.RemoteAddr, id.SessionID())
inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(&id, r))
return
}
st, err := getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego, r, w)
if st == nil || err != nil {
// response to client and logging handled in function above so just return
return
}
// 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)
return
}
if status.Code == gssapi.StatusContinueNeeded {
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr)
return
}
if authed {
// Authentication successful; get user's credentials from the context
id := ctx.Value(ctxCredentials).(*credentials.Credentials)
// Create a new session if a session manager has been configured
err = newSession(spnego, r, w, id)
if err != nil {
return
}
spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
// Add the identity to the context and serve the inner/wrapped handler
inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(id, r))
return
}
// If we get to here we have not authenticationed so just reject
spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr)
return
})
}
func getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego *SPNEGO, r *http.Request, w http.ResponseWriter) (*SPNEGOToken, error) {
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 nil, errors.New("client did not provide a negotiation authorization header")
}
// Decode the header into an SPNEGO context token
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
err = fmt.Errorf("error in base64 decoding negotiation header: %v", err)
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err)
return nil, err
}
var st SPNEGOToken
err = st.Unmarshal(b)
if err != nil {
// Check if this is a raw KRB5 context token - issue #347.
var k5t KRB5Token
if k5t.Unmarshal(b) != nil {
err = fmt.Errorf("error in unmarshaling SPNEGO token: %v", err)
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err)
return nil, err
}
// Wrap it into an SPNEGO context token
st.Init = true
st.NegTokenInit = NegTokenInit{
MechTypes: []asn1.ObjectIdentifier{k5t.OID},
MechTokenBytes: b,
}
}
return &st, nil
}
func getSessionCredentials(spnego *SPNEGO, r *http.Request) (credentials.Credentials, error) {
var creds credentials.Credentials
// Check if there is a session manager and if there is an already established session for this client
if sm := spnego.serviceSettings.SessionManager(); sm != nil {
cb, err := sm.Get(r, sessionCredentials)
if err != nil || cb == nil || len(cb) < 1 {
return creds, fmt.Errorf("%s - SPNEGO error getting session and credentials for request: %v", r.RemoteAddr, err)
}
err = creds.Unmarshal(cb)
if err != nil {
return creds, fmt.Errorf("%s - SPNEGO credentials malformed in session: %v", r.RemoteAddr, err)
}
return creds, nil
}
return creds, errors.New("no session manager configured")
}
func newSession(spnego *SPNEGO, r *http.Request, w http.ResponseWriter, id *credentials.Credentials) error {
if sm := spnego.serviceSettings.SessionManager(); sm != nil {
// create new session
idb, err := id.Marshal()
if err != nil {
spnegoInternalServerError(spnego, w, "SPNEGO could not marshal credentials to add to the session: %v", err)
return err
}
err = sm.New(w, r, sessionCredentials, idb)
if err != nil {
spnegoInternalServerError(spnego, w, "SPNEGO could not create new session: %v", err)
return err
}
spnego.Log("%s %s@%s - SPNEGO new session (%s) created", r.RemoteAddr, id.UserName(), id.Domain(), id.SessionID())
}
return nil
}
// Log and respond to client for error conditions
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)
}
func spnegoInternalServerError(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
s.Log(format, v...)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}

View File

@ -0,0 +1,218 @@
package spnego
import (
"context"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/jcmturner/gokrb5/v8/asn1tools"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
"github.com/jcmturner/gokrb5/v8/iana/msgtype"
"github.com/jcmturner/gokrb5/v8/krberror"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/service"
"github.com/jcmturner/gokrb5/v8/types"
)
// GSSAPI KRB5 MechToken IDs.
const (
TOK_ID_KRB_AP_REQ = "0100"
TOK_ID_KRB_AP_REP = "0200"
TOK_ID_KRB_ERROR = "0300"
)
// KRB5Token context token implementation for GSSAPI.
type KRB5Token struct {
OID asn1.ObjectIdentifier
tokID []byte
APReq messages.APReq
APRep messages.APRep
KRBError messages.KRBError
settings *service.Settings
context context.Context
}
// 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...)
var tb []byte
var err error
switch hex.EncodeToString(m.tokID) {
case TOK_ID_KRB_AP_REQ:
tb, err = m.APReq.Marshal()
if err != nil {
return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err)
}
case TOK_ID_KRB_AP_REP:
return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5")
case TOK_ID_KRB_ERROR:
return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5")
}
if err != nil {
return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err)
}
b = append(b, tb...)
return asn1tools.AddASNAppTag(b, 0), nil
}
// 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 KRB5Token OID: %v", err)
}
if !oid.Equal(gssapi.OIDKRB5.OID()) {
return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OIDKRB5.OID().String())
}
m.OID = oid
if len(r) < 2 {
return fmt.Errorf("krb5token too short")
}
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 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 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 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, ctxCredentials, creds)
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 *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 *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 *KRB5Token) IsKRBError() bool {
if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR {
return true
}
return false
}
// 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.OIDKRB5.OID()
tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
m.tokID = tb
auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags)
if err != nil {
return m, err
}
APReq, err := messages.NewAPReq(
tkt,
sessionKey,
auth,
)
if err != nil {
return m, err
}
for _, o := range APOptions {
types.SetFlag(&APReq.APOptions, o)
}
m.APReq = APReq
return m, nil
}
// 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.Domain(), creds.CName())
if err != nil {
return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
}
auth.Cksum = types.Checksum{
CksumType: chksumtype.GSSAPI,
Checksum: newAuthenticatorChksum(flags),
}
return auth, nil
}
// Create new authenticator checksum for kerberos MechToken
func newAuthenticatorChksum(flags []int) []byte {
a := make([]byte, 24)
binary.LittleEndian.PutUint32(a[:4], 16)
for _, i := range flags {
if i == gssapi.ContextFlagDeleg {
x := make([]byte, 28-len(a))
a = append(a, x...)
}
f := binary.LittleEndian.Uint32(a[20:24])
f |= uint32(i)
binary.LittleEndian.PutUint32(a[20:24], f)
}
return a
}

View File

@ -0,0 +1,302 @@
package spnego
import (
"context"
"errors"
"fmt"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/service"
"github.com/jcmturner/gokrb5/v8/types"
)
// https://msdn.microsoft.com/en-us/library/ms995330.aspx
// 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
var mtSupported bool
for _, m := range n.MechTypes {
if m.Equal(gssapi.OIDKRB5.OID()) || m.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
if n.mechToken == nil && n.MechTokenBytes == nil {
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
}
mtSupported = true
break
}
}
if !mtSupported {
return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
}
// There should be some mechtoken bytes for a KRB5Token (other mech types are not supported)
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"}
}
}
// Verify the mechtoken
return n.mechToken.Verify()
}
// 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.OIDKRB5.OID()) || n.SupportedMech.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
if n.mechToken == nil && n.ResponseToken == nil {
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
}
mt := new(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"}
}
}
if mt == nil {
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
}
// 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.OIDKRB5.OID()},
MechTokenBytes: mtb,
}, nil
}

203
vendor/github.com/jcmturner/gokrb5/v8/spnego/spnego.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication.
package spnego
import (
"context"
"errors"
"fmt"
"github.com/jcmturner/gofork/encoding/asn1"
"github.com/jcmturner/gokrb5/v8/asn1tools"
"github.com/jcmturner/gokrb5/v8/client"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/service"
)
// SPNEGO implements the GSS-API mechanism for RFC 4178
type SPNEGO struct {
serviceSettings *service.Settings
client *client.Client
spn string
}
// SPNEGOClient configures the SPNEGO mechanism suitable for client side use.
func SPNEGOClient(cl *client.Client, spn string) *SPNEGO {
s := new(SPNEGO)
s.client = cl
s.spn = spn
s.serviceSettings = service.NewSettings(nil, service.SName(spn))
return s
}
// SPNEGOService configures the SPNEGO mechanism suitable for service side use.
func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO {
s := new(SPNEGO)
s.serviceSettings = service.NewSettings(kt, options...)
return s
}
// OID returns the GSS-API assigned OID for SPNEGO.
func (s *SPNEGO) OID() asn1.ObjectIdentifier {
return gssapi.OIDSPNEGO.OID()
}
// AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO.
func (s *SPNEGO) AcquireCred() error {
return s.client.AffirmLogin()
}
// InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos.
func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) {
tkt, key, err := s.client.GetServiceTicket(s.spn)
if err != nil {
return &SPNEGOToken{}, err
}
negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key)
if err != nil {
return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err)
}
return &SPNEGOToken{
Init: true,
NegTokenInit: negTokenInit,
settings: s.serviceSettings,
}, nil
}
// AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and
// establish a context.
func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) {
var ctx context.Context
t, ok := ct.(*SPNEGOToken)
if !ok {
return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"}
}
t.settings = s.serviceSettings
var oid asn1.ObjectIdentifier
if t.Init {
oid = t.NegTokenInit.MechTypes[0]
}
if t.Resp {
oid = t.NegTokenResp.SupportedMech
}
if !(oid.Equal(gssapi.OIDKRB5.OID()) || oid.Equal(gssapi.OIDMSLegacyKRB5.OID())) {
return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"}
}
// Flags in the NegInit must be used t.NegTokenInit.ReqFlags
ok, status := t.Verify()
ctx = t.Context()
return ok, ctx, status
}
// Log will write to the service's logger if it is configured.
func (s *SPNEGO) Log(format string, v ...interface{}) {
if s.serviceSettings.Logger() != nil {
s.serviceSettings.Logger().Output(2, fmt.Sprintf(format, v...))
}
}
// SPNEGOToken is a GSS-API context token
type SPNEGOToken struct {
Init bool
Resp bool
NegTokenInit NegTokenInit
NegTokenResp NegTokenResp
settings *service.Settings
context context.Context
}
// Marshal SPNEGO context token
func (s *SPNEGOToken) Marshal() ([]byte, error) {
var b []byte
if s.Init {
hb, _ := asn1.Marshal(gssapi.OIDSPNEGO.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
}
return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp")
}
// Unmarshal SPNEGO context token
func (s *SPNEGOToken) Unmarshal(b []byte) error {
var r []byte
var err error
// We need some data in the array
if len(b) < 1 {
return fmt.Errorf("provided byte array is empty")
}
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
SPNEGOOID := gssapi.OIDSPNEGO.OID()
if !oid.Equal(SPNEGOOID) {
return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String())
}
} else {
// Could be a NegTokenResp/Targ
r = b
}
_, nt, err := UnmarshalNegToken(r)
if err != nil {
return err
}
switch v := nt.(type) {
case NegTokenInit:
s.Init = true
s.NegTokenInit = v
s.NegTokenInit.settings = s.settings
case NegTokenResp:
s.Resp = true
s.NegTokenResp = v
s.NegTokenResp.settings = s.settings
default:
return errors.New("unknown choice type for NegotiationToken")
}
return nil
}
// Verify the SPNEGOToken
func (s *SPNEGOToken) Verify() (bool, gssapi.Status) {
if (!s.Init && !s.Resp) || (s.Init && s.Resp) {
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"}
}
if s.Init {
s.NegTokenInit.settings = s.settings
ok, status := s.NegTokenInit.Verify()
if ok {
s.context = s.NegTokenInit.Context()
}
return ok, status
}
if s.Resp {
s.NegTokenResp.settings = s.settings
ok, status := s.NegTokenResp.Verify()
if ok {
s.context = s.NegTokenResp.Context()
}
return ok, status
}
// should not be possible to get here
return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"}
}
// Context returns the SPNEGO context which will contain any verify user identity information.
func (s *SPNEGOToken) Context() context.Context {
return s.context
}