mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-09-27 19:52:18 +08:00
234 lines
5.6 KiB
Go
234 lines
5.6 KiB
Go
package ssh
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jcmturner/gokrb5/v8/client"
|
|
"github.com/jcmturner/gokrb5/v8/config"
|
|
"github.com/jcmturner/gokrb5/v8/credentials"
|
|
"github.com/jcmturner/gokrb5/v8/crypto"
|
|
"github.com/jcmturner/gokrb5/v8/gssapi"
|
|
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
|
|
"github.com/jcmturner/gokrb5/v8/iana/flags"
|
|
"github.com/jcmturner/gokrb5/v8/keytab"
|
|
"github.com/jcmturner/gokrb5/v8/messages"
|
|
"github.com/jcmturner/gokrb5/v8/spnego"
|
|
"github.com/jcmturner/gokrb5/v8/types"
|
|
)
|
|
|
|
type Krb5ClientState int
|
|
|
|
const (
|
|
ContextFlagREADY = 128
|
|
/* initiator states */
|
|
InitiatorStart Krb5ClientState = iota
|
|
InitiatorRestart
|
|
InitiatorWaitForMutal
|
|
InitiatorReady
|
|
)
|
|
|
|
func NewKrb5InitiatorClientWithPassword(username, password, krb5Conf string) (kcl Krb5InitiatorClient, err error) {
|
|
c, err := config.Load(krb5Conf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defaultRealm := c.LibDefaults.DefaultRealm
|
|
|
|
cl := client.NewWithPassword(username, defaultRealm, password, c)
|
|
err = cl.Login()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = cl.AffirmLogin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return Krb5InitiatorClient{
|
|
client: cl,
|
|
state: InitiatorStart,
|
|
}, nil
|
|
}
|
|
|
|
func NewKrb5InitiatorClientWithKeytab(username string, krb5Conf, keytabConf string) (kcl Krb5InitiatorClient, err error) {
|
|
c, err := config.Load(krb5Conf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Init keytab from conf
|
|
cache, err := keytab.Load(keytabConf)
|
|
if err != nil {
|
|
return kcl, fmt.Errorf("unmarshal keytabConf failed: %w", err)
|
|
}
|
|
|
|
defaultRealm := c.LibDefaults.DefaultRealm
|
|
|
|
cl := client.NewWithKeytab(username, defaultRealm, cache, c)
|
|
err = cl.Login()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = cl.AffirmLogin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return Krb5InitiatorClient{
|
|
client: cl,
|
|
state: InitiatorStart,
|
|
}, nil
|
|
}
|
|
|
|
func NewKrb5InitiatorClientWithCache(krb5Conf, cacheFile string) (kcl Krb5InitiatorClient, err error) {
|
|
c, err := config.Load(krb5Conf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Init krb5 client and login
|
|
cache, err := credentials.LoadCCache(cacheFile)
|
|
// https://stackoverflow.com/questions/58653482/what-is-the-default-kerberos-credential-cache-on-osx
|
|
if err != nil {
|
|
return
|
|
}
|
|
cl, err := client.NewFromCCache(cache, c)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = cl.Login()
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = cl.AffirmLogin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return Krb5InitiatorClient{
|
|
client: cl,
|
|
state: InitiatorStart,
|
|
}, nil
|
|
}
|
|
|
|
type Krb5InitiatorClient struct {
|
|
state Krb5ClientState
|
|
client *client.Client
|
|
subkey types.EncryptionKey
|
|
}
|
|
|
|
// Create new authenticator checksum for kerberos MechToken
|
|
func (k *Krb5InitiatorClient) 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
|
|
}
|
|
|
|
func (k *Krb5InitiatorClient) InitSecContext(target string, token []byte, isGSSDelegCreds bool) ([]byte, bool, error) {
|
|
GSSAPIFlags := []int{
|
|
ContextFlagREADY,
|
|
gssapi.ContextFlagInteg,
|
|
gssapi.ContextFlagMutual,
|
|
}
|
|
if isGSSDelegCreds {
|
|
GSSAPIFlags = append(GSSAPIFlags, gssapi.ContextFlagDeleg)
|
|
}
|
|
APOptions := []int{flags.APOptionMutualRequired}
|
|
|
|
switch k.state {
|
|
case InitiatorStart, InitiatorRestart:
|
|
newTarget := strings.ReplaceAll(target, "@", "/")
|
|
|
|
tkt, sessionKey, err := k.client.GetServiceTicket(newTarget)
|
|
if err != nil {
|
|
return []byte{}, false, err
|
|
}
|
|
|
|
krb5Token, err := spnego.NewKRB5TokenAPREQ(k.client, tkt, sessionKey, GSSAPIFlags, APOptions)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("error generating new kerberos 5 token: %w", err)
|
|
}
|
|
creds := k.client.Credentials
|
|
auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("error generating new authenticator: %w", err)
|
|
}
|
|
auth.Cksum = types.Checksum{
|
|
CksumType: chksumtype.GSSAPI,
|
|
Checksum: k.newAuthenticatorChksum(GSSAPIFlags),
|
|
}
|
|
etype, _ := crypto.GetEtype(sessionKey.KeyType)
|
|
if err := auth.GenerateSeqNumberAndSubKey(sessionKey.KeyType, etype.GetKeyByteSize()); err != nil {
|
|
return nil, false, err
|
|
}
|
|
k.subkey = auth.SubKey
|
|
|
|
APReq, err := messages.NewAPReq(
|
|
tkt,
|
|
sessionKey,
|
|
auth,
|
|
)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("error generating NewAPReq: %w", err)
|
|
}
|
|
for _, o := range APOptions {
|
|
types.SetFlag(&APReq.APOptions, o)
|
|
}
|
|
krb5Token.APReq = APReq
|
|
|
|
outToken, err := krb5Token.Marshal()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return []byte{}, false, err
|
|
}
|
|
k.state = InitiatorWaitForMutal
|
|
return outToken, true, nil
|
|
case InitiatorWaitForMutal:
|
|
var krb5Token spnego.KRB5Token
|
|
if err := krb5Token.Unmarshal(token); err != nil {
|
|
err := fmt.Errorf("unmarshal APRep token failed: %w", err)
|
|
return []byte{}, false, err
|
|
}
|
|
//var enc messages.EncAPRepPart
|
|
//err2 := enc.Unmarshal(krb5Token.APRep.EncPart.Cipher)
|
|
//fmt.Printf("err2: %#v, enc: %#v\n", err2, enc)
|
|
|
|
k.state = InitiatorReady
|
|
return []byte{}, false, nil
|
|
case InitiatorReady:
|
|
return nil, false, fmt.Errorf("called one time too many, client has already been %d", k.state)
|
|
default:
|
|
return nil, false, fmt.Errorf("invalid state %d", k.state)
|
|
}
|
|
}
|
|
|
|
func (k *Krb5InitiatorClient) GetMIC(micFiled []byte) ([]byte, error) {
|
|
micToken, err := gssapi.NewInitiatorMICToken(micFiled, k.subkey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
token, err := micToken.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (k *Krb5InitiatorClient) DeleteSecContext() error {
|
|
k.client.Destroy()
|
|
return nil
|
|
}
|