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:
committed by
Han-Wen Nienhuys
parent
e1dfcc5662
commit
cbcb750295
+214
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user