vikunja-api/vendor/github.com/jcmturner/gokrb5/v8/spnego/spnego.go
2020-05-29 17:47:28 +00:00

203 lines
5.9 KiB
Go

// 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
}