Skip to content

Instantly share code, notes, and snippets.

@9072997
Created January 24, 2021 00:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 9072997/d9596aacccb63c77e15ecdcd2565617b to your computer and use it in GitHub Desktop.
Save 9072997/d9596aacccb63c77e15ecdcd2565617b to your computer and use it in GitHub Desktop.
Re-produce what I think is a bug in golang.org/x/crypto/ssh
package main
import (
"fmt"
"log"
"net"
"time"
"golang.org/x/crypto/ssh"
)
const HostKey = `
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
`
// AllowLogin, must be set, is called when gssapi-with-mic
// authentication is selected (RFC 4462 section 3). The srcName is from the
// results of the GSS-API authentication. The format is username@DOMAIN.
// GSSAPI just guarantees to the server who the user is, but not if they can log in, and with what permissions.
// This callback is called after the user identity is established with GSSAPI to decide if the user can login with
// which permissions. If the user is allowed to login, it should return a nil error.
func AllowLogin(
conn ssh.ConnMetadata, srcName string,
) (
*ssh.Permissions, error,
) {
fmt.Printf("AllowLogin(__, %s)\n", srcName)
fmt.Println(conn)
return nil, nil
}
func server() {
sshConfig := &ssh.ServerConfig{
GSSAPIWithMICConfig: &ssh.GSSAPIWithMICConfig{
AllowLogin: AllowLogin,
Server: &GSSAPIServer{},
},
}
// use host key from config file
hostKeyData := []byte(HostKey)
hostKey, err := ssh.ParsePrivateKey(hostKeyData)
if err != nil {
panic(err)
}
sshConfig.AddHostKey(hostKey)
// start a TCP server on the IP and port from the config
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
IP: nil,
Port: 2222,
})
if err != nil {
panic(err)
}
// accept connection
tcpConn, err := tcpListener.Accept()
if err != nil {
panic(err)
}
// upgrade the tcp connection to an ssh connection
sshConn, _, _, err := ssh.NewServerConn(tcpConn, sshConfig)
if err != nil {
panic(err)
}
log.Printf("New connection from %s", sshConn.RemoteAddr())
// normally we would do something with the connection here, but this is
// just a demo
time.Sleep(10 * time.Second)
}
func client() {
sshConn, err := ssh.Dial(
"tcp",
"localhost:2222",
&ssh.ClientConfig{
User: "UsernameSuppliedByClient",
Auth: []ssh.AuthMethod{
ssh.GSSAPIWithMICAuthMethod(&GSSAPIClient{}, "HostnameSuppliedByClient"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
},
)
if err != nil {
panic(err)
}
time.Sleep(10 * time.Second)
sshConn.Close()
}
func main() {
go server()
time.Sleep(time.Second)
client()
}
////////////////////////////////////////////////////////////////////////////
type GSSAPIServer struct {
counter byte
}
// AcceptSecContext allows a remotely initiated security context between the application
// and a remote peer to be established by the ssh client. The routine may return a
// outputToken which should be transferred to the ssh client,
// where the ssh client will present it to InitSecContext.
// If no token need be sent, AcceptSecContext will indicate this
// by setting the needContinue to false. To
// complete the context establishment, one or more reply tokens may be
// required from the ssh client. if so, AcceptSecContext
// will return a needContinue which is true, in which case it
// should be called again when the reply token is received from the ssh
// client, passing the token to AcceptSecContext via the
// token parameters.
// The srcName return value is the authenticated username.
// See RFC 2743 section 2.2.2 and RFC 4462 section 3.4.
func (g *GSSAPIServer) AcceptSecContext(
token []byte,
) (
outputToken []byte, srcName string, needContinue bool, err error,
) {
g.counter++
outputToken = []byte{token[0] - 50, g.counter}
srcName = "UsernameDeterminedByServer"
needContinue = true
err = nil
fmt.Printf("SERVER: AcceptSecContext(%v): %v, %s, %t, %v\n",
token, outputToken, srcName, needContinue, err,
)
return
}
// VerifyMIC verifies that a cryptographic MIC, contained in the token parameter,
// fits the supplied message is received from the ssh client.
// See RFC 2743 section 2.3.2.
func (g *GSSAPIServer) VerifyMIC(micField []byte, micToken []byte) error {
fmt.Printf("SERVER: VerifyMIC(%v, %v)\n", micField, micToken)
return nil
}
// Whenever possible, it should be possible for
// DeleteSecContext() calls to be successfully processed even
// if other calls cannot succeed, thereby enabling context-related
// resources to be released.
// In addition to deleting established security contexts,
// gss_delete_sec_context must also be able to delete "half-built"
// security contexts resulting from an incomplete sequence of
// InitSecContext()/AcceptSecContext() calls.
// See RFC 2743 section 2.2.3.
func (g *GSSAPIServer) DeleteSecContext() error {
fmt.Println("SERVER: DeleteSecContext()")
return nil
}
////////////////////////////////////////////////////////////////////////////
type GSSAPIClient struct {
counter byte
}
// InitSecContext initiates the establishment of a security context for GSS-API between the
// ssh client and ssh server. Initially the token parameter should be specified as nil.
// The routine may return a outputToken which should be transferred to
// the ssh server, where the ssh server will present it to
// AcceptSecContext. If no token need be sent, InitSecContext will indicate this by setting
// needContinue to false. To complete the context
// establishment, one or more reply tokens may be required from the ssh
// server;if so, InitSecContext will return a needContinue which is true.
// In this case, InitSecContext should be called again when the
// reply token is received from the ssh server, passing the reply
// token to InitSecContext via the token parameters.
// See RFC 2743 section 2.2.1 and RFC 4462 section 3.4.
func (g *GSSAPIClient) InitSecContext(
target string, token []byte, isGSSDelegCreds bool,
) (
outputToken []byte, needContinue bool, err error,
) {
if token == nil {
g.counter = 100
outputToken = []byte{g.counter}
needContinue = true
err = nil
} else {
g.counter++
if g.counter == 110 {
outputToken = nil
needContinue = false
err = nil
} else {
outputToken = []byte{g.counter}
needContinue = true
err = nil
}
}
fmt.Printf("CLIENT: InitSecContext(%s, %v, %t): %v %t %v\n",
target, token, isGSSDelegCreds, outputToken, needContinue, err,
)
return
}
// GetMIC generates a cryptographic MIC for the SSH2 message, and places
// the MIC in a token for transfer to the ssh server.
// The contents of the MIC field are obtained by calling GSS_GetMIC()
// over the following, using the GSS-API context that was just
// established:
// string session identifier
// byte SSH_MSG_USERAUTH_REQUEST
// string user name
// string service
// string "gssapi-with-mic"
// See RFC 2743 section 2.3.1 and RFC 4462 3.5.
func (g *GSSAPIClient) GetMIC(micFiled []byte) ([]byte, error) {
fmt.Printf("CLIENT: GetMIC(%v)\n", micFiled)
return []byte{90, 72, 99, 7}, nil
}
// Whenever possible, it should be possible for
// DeleteSecContext() calls to be successfully processed even
// if other calls cannot succeed, thereby enabling context-related
// resources to be released.
// In addition to deleting established security contexts,
// gss_delete_sec_context must also be able to delete "half-built"
// security contexts resulting from an incomplete sequence of
// InitSecContext()/AcceptSecContext() calls.
// See RFC 2743 section 2.2.3.
func (g *GSSAPIClient) DeleteSecContext() error {
fmt.Println("CLIENT: DeleteSecContext()")
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment