ssh/gss: support kerberos authentication for ssh server and client

Change-Id: I20e3356476dc50402dd34d2b39ad030c1e63a9ef
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/170919
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
This commit is contained in:
yanweizhi
2019-05-10 10:41:15 +00:00
committed by Han-Wen Nienhuys
parent e1dfcc5662
commit cbcb750295
6 changed files with 737 additions and 5 deletions
+214 -4
View File
@@ -33,12 +33,19 @@ var clientPassword = "tiger"
// tryAuth runs a handshake with a given config against an SSH server
// with config serverConfig. Returns both client and server side errors.
func tryAuth(t *testing.T, config *ClientConfig) error {
err, _ := tryAuthBothSides(t, config)
err, _ := tryAuthBothSides(t, config, nil)
return err
}
// tryAuth runs a handshake with a given config against an SSH server
// with a given GSSAPIWithMICConfig and config serverConfig. Returns both client and server side errors.
func tryAuthWithGSSAPIWithMICConfig(t *testing.T, clientConfig *ClientConfig, gssAPIWithMICConfig *GSSAPIWithMICConfig) error {
err, _ := tryAuthBothSides(t, clientConfig, gssAPIWithMICConfig)
return err
}
// tryAuthBothSides runs the handshake and returns the resulting errors from both sides of the connection.
func tryAuthBothSides(t *testing.T, config *ClientConfig) (clientError error, serverAuthErrors []error) {
func tryAuthBothSides(t *testing.T, config *ClientConfig, gssAPIWithMICConfig *GSSAPIWithMICConfig) (clientError error, serverAuthErrors []error) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
@@ -61,7 +68,6 @@ func tryAuthBothSides(t *testing.T, config *ClientConfig) (clientError error, se
return c.Serial == 666
},
}
serverConfig := &ServerConfig{
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
if conn.User() == "testuser" && string(pass) == clientPassword {
@@ -85,6 +91,7 @@ func tryAuthBothSides(t *testing.T, config *ClientConfig) (clientError error, se
}
return nil, errors.New("keyboard-interactive failed")
},
GSSAPIWithMICConfig: gssAPIWithMICConfig,
}
serverConfig.AddHostKey(testSigners["rsa"])
@@ -247,7 +254,7 @@ func TestMethodInvalidAlgorithm(t *testing.T) {
HostKeyCallback: InsecureIgnoreHostKey(),
}
err, serverErrors := tryAuthBothSides(t, config)
err, serverErrors := tryAuthBothSides(t, config, nil)
if err == nil {
t.Fatalf("login succeeded")
}
@@ -686,3 +693,206 @@ func TestClientAuthErrorList(t *testing.T) {
}
}
}
func TestAuthMethodGSSAPIWithMIC(t *testing.T) {
type testcase struct {
config *ClientConfig
gssConfig *GSSAPIWithMICConfig
clientWantErr string
serverWantErr string
}
testcases := []*testcase{
{
config: &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
GSSAPIWithMICAuthMethod(
&FakeClient{
exchanges: []*exchange{
{
outToken: "client-valid-token-1",
},
{
expectedToken: "server-valid-token-1",
},
},
mic: []byte("valid-mic"),
maxRound: 2,
}, "testtarget",
),
},
HostKeyCallback: InsecureIgnoreHostKey(),
},
gssConfig: &GSSAPIWithMICConfig{
AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
if srcName != conn.User()+"@DOMAIN" {
return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
}
return nil, nil
},
Server: &FakeServer{
exchanges: []*exchange{
{
outToken: "server-valid-token-1",
expectedToken: "client-valid-token-1",
},
},
maxRound: 1,
expectedMIC: []byte("valid-mic"),
srcName: "testuser@DOMAIN",
},
},
},
{
config: &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
GSSAPIWithMICAuthMethod(
&FakeClient{
exchanges: []*exchange{
{
outToken: "client-valid-token-1",
},
{
expectedToken: "server-valid-token-1",
},
},
mic: []byte("valid-mic"),
maxRound: 2,
}, "testtarget",
),
},
HostKeyCallback: InsecureIgnoreHostKey(),
},
gssConfig: &GSSAPIWithMICConfig{
AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
return nil, fmt.Errorf("user is not allowed to login")
},
Server: &FakeServer{
exchanges: []*exchange{
{
outToken: "server-valid-token-1",
expectedToken: "client-valid-token-1",
},
},
maxRound: 1,
expectedMIC: []byte("valid-mic"),
srcName: "testuser@DOMAIN",
},
},
serverWantErr: "user is not allowed to login",
clientWantErr: "ssh: handshake failed: ssh: unable to authenticate",
},
{
config: &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
GSSAPIWithMICAuthMethod(
&FakeClient{
exchanges: []*exchange{
{
outToken: "client-valid-token-1",
},
{
expectedToken: "server-valid-token-1",
},
},
mic: []byte("valid-mic"),
maxRound: 2,
}, "testtarget",
),
},
HostKeyCallback: InsecureIgnoreHostKey(),
},
gssConfig: &GSSAPIWithMICConfig{
AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
if srcName != conn.User() {
return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
}
return nil, nil
},
Server: &FakeServer{
exchanges: []*exchange{
{
outToken: "server-invalid-token-1",
expectedToken: "client-valid-token-1",
},
},
maxRound: 1,
expectedMIC: []byte("valid-mic"),
srcName: "testuser@DOMAIN",
},
},
clientWantErr: "ssh: handshake failed: got \"server-invalid-token-1\", want token \"server-valid-token-1\"",
},
{
config: &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
GSSAPIWithMICAuthMethod(
&FakeClient{
exchanges: []*exchange{
{
outToken: "client-valid-token-1",
},
{
expectedToken: "server-valid-token-1",
},
},
mic: []byte("invalid-mic"),
maxRound: 2,
}, "testtarget",
),
},
HostKeyCallback: InsecureIgnoreHostKey(),
},
gssConfig: &GSSAPIWithMICConfig{
AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
if srcName != conn.User() {
return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
}
return nil, nil
},
Server: &FakeServer{
exchanges: []*exchange{
{
outToken: "server-valid-token-1",
expectedToken: "client-valid-token-1",
},
},
maxRound: 1,
expectedMIC: []byte("valid-mic"),
srcName: "testuser@DOMAIN",
},
},
serverWantErr: "got MICToken \"invalid-mic\", want \"valid-mic\"",
clientWantErr: "ssh: handshake failed: ssh: unable to authenticate",
},
}
for i, c := range testcases {
clientErr, serverErrs := tryAuthBothSides(t, c.config, c.gssConfig)
if (c.clientWantErr == "") != (clientErr == nil) {
t.Fatalf("client got %v, want %s, case %d", clientErr, c.clientWantErr, i)
}
if (c.serverWantErr == "") != (len(serverErrs) == 2 && serverErrs[1] == nil || len(serverErrs) == 1) {
t.Fatalf("server got err %v, want %s", serverErrs, c.serverWantErr)
}
if c.clientWantErr != "" {
if clientErr != nil && !strings.Contains(clientErr.Error(), c.clientWantErr) {
t.Fatalf("client got %v, want %s, case %d", clientErr, c.clientWantErr, i)
}
}
found := false
var errStrings []string
if c.serverWantErr != "" {
for _, err := range serverErrs {
found = found || (err != nil && strings.Contains(err.Error(), c.serverWantErr))
errStrings = append(errStrings, err.Error())
}
if !found {
t.Errorf("server got error %q, want substring %q, case %d", errStrings, c.serverWantErr, i)
}
}
}
}