telebit/vendor/github.com/jcmturner/gokrb5/v8/spnego/krb5Token.go

219 lines
6.3 KiB
Go

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
}