diff --git a/auth.go b/auth.go index da2d5d1..5f0bc2b 100644 --- a/auth.go +++ b/auth.go @@ -5,6 +5,7 @@ package goph import ( "fmt" + "io" "io/ioutil" "net" "os" @@ -12,6 +13,9 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/config" + "gopkg.in/jcmturner/gokrb5.v7/keytab" ) // Auth represents ssh auth methods. @@ -66,6 +70,44 @@ func RawKey(privateKey string, passphrase string) (Auth, error) { }, nil } +// KerberosWithPassword returns a kerberos auth with password. +func KerberosWithPassword(username, password, realm, target string, krb5cfg io.Reader) (Auth, error) { + cfg, err := config.NewConfigFromReader(krb5cfg) + if err != nil { + return nil, err + } + + cl := client.NewClientWithPassword(username, realm, password, cfg, client.DisablePAFXFAST(true)) + c, err := newKrb5Client(cl) + if err != nil { + return nil, err + } + return Auth{ + ssh.GSSAPIWithMICAuthMethod(c, target), + }, nil +} + +// KerberosWithKeytab returns a kerberos auth with keytab. +func KerberosWithKeytab(username, keytabFile, realm, target string, krb5cfg io.Reader) (Auth, error) { + kt, err := keytab.Load(keytabFile) + if err != nil { + return nil, err + } + cfg, err := config.NewConfigFromReader(krb5cfg) + if err != nil { + return nil, err + } + + cl := client.NewClientWithKeytab(username, realm, kt, cfg, client.DisablePAFXFAST(true)) + c, err := newKrb5Client(cl) + if err != nil { + return nil, err + } + return Auth{ + ssh.GSSAPIWithMICAuthMethod(c, target), + }, nil +} + // HasAgent checks if ssh agent exists. func HasAgent() bool { return os.Getenv("SSH_AUTH_SOCK") != "" diff --git a/go.mod b/go.mod index f7e98dc..2dffbf3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,14 @@ module github.com/melbahja/goph go 1.13 require ( + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.13.5 golang.org/x/crypto v0.6.0 + gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect + gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect + gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect + gopkg.in/jcmturner/gokrb5.v7 v7.5.0 + gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index eea2896..7b6e80c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -49,5 +53,15 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/krb5.go b/krb5.go new file mode 100644 index 0000000..da41a7a --- /dev/null +++ b/krb5.go @@ -0,0 +1,70 @@ +// Copyright 2024 Mohammed El Bahja. All rights reserved. +// Use of this source code is governed by a MIT license. + +package goph + +import ( + "strings" + + "gopkg.in/jcmturner/gokrb5.v7/client" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/spnego" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +type krb5Client struct { + client *client.Client + skey types.EncryptionKey + gen bool +} + +func newKrb5Client(c *client.Client) (*krb5Client, error) { + if err := c.Login(); err != nil { + return nil, err + } + return &krb5Client{client: c}, nil +} + +func (c *krb5Client) InitSecContext(target string, token []byte, isGSSDelegCreds bool) (outputToken []byte, needContinue bool, err error) { + if c.gen { + return nil, false, nil + } + + t := strings.Replace(target, "@", "/", 1) + tkt, skey, err := c.client.GetServiceTicket(t) + if err != nil { + return nil, false, err + } + c.skey = skey + + gssApiFlags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagMutual} + if isGSSDelegCreds { + gssApiFlags = append(gssApiFlags, gssapi.ContextFlagDeleg) + } + + krb5Tkn, err := spnego.NewKRB5TokenAPREQ(c.client, tkt, skey, gssApiFlags, nil) + if err != nil { + return nil, false, err + } + + outputToken, err = krb5Tkn.Marshal() + if err != nil { + return nil, false, err + } + c.gen = true + + return outputToken, true, nil +} + +func (c *krb5Client) GetMIC(micFiled []byte) ([]byte, error) { + micTkn, err := gssapi.NewInitiatorMICToken(micFiled, c.skey) + if err != nil { + return nil, err + } + return micTkn.Marshal() +} + +func (c *krb5Client) DeleteSecContext() error { + c.client.Destroy() + return nil +}