mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
9.8 KiB
393 lines
9.8 KiB
// Copyright 2011 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package ssh |
|
|
|
import ( |
|
"bytes" |
|
"crypto/rand" |
|
"errors" |
|
"fmt" |
|
"strings" |
|
"testing" |
|
) |
|
|
|
type keyboardInteractive map[string]string |
|
|
|
func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) { |
|
var answers []string |
|
for _, q := range questions { |
|
answers = append(answers, cr[q]) |
|
} |
|
return answers, nil |
|
} |
|
|
|
// reused internally by tests |
|
var clientPassword = "tiger" |
|
|
|
// tryAuth runs a handshake with a given config against an SSH server |
|
// with config serverConfig |
|
func tryAuth(t *testing.T, config *ClientConfig) error { |
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
certChecker := CertChecker{ |
|
IsAuthority: func(k PublicKey) bool { |
|
return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal()) |
|
}, |
|
UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { |
|
if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) { |
|
return nil, nil |
|
} |
|
|
|
return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User()) |
|
}, |
|
IsRevoked: func(c *Certificate) bool { |
|
return c.Serial == 666 |
|
}, |
|
} |
|
|
|
serverConfig := &ServerConfig{ |
|
PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) { |
|
if conn.User() == "testuser" && string(pass) == clientPassword { |
|
return nil, nil |
|
} |
|
return nil, errors.New("password auth failed") |
|
}, |
|
PublicKeyCallback: certChecker.Authenticate, |
|
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) { |
|
ans, err := challenge("user", |
|
"instruction", |
|
[]string{"question1", "question2"}, |
|
[]bool{true, true}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2" |
|
if ok { |
|
challenge("user", "motd", nil, nil) |
|
return nil, nil |
|
} |
|
return nil, errors.New("keyboard-interactive failed") |
|
}, |
|
AuthLogCallback: func(conn ConnMetadata, method string, err error) { |
|
t.Logf("user %q, method %q: %v", conn.User(), method, err) |
|
}, |
|
} |
|
serverConfig.AddHostKey(testSigners["rsa"]) |
|
|
|
go newServer(c1, serverConfig) |
|
_, _, _, err = NewClientConn(c2, "", config) |
|
return err |
|
} |
|
|
|
func TestClientAuthPublicKey(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["rsa"]), |
|
}, |
|
} |
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("unable to dial remote side: %s", err) |
|
} |
|
} |
|
|
|
func TestAuthMethodPassword(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
Password(clientPassword), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("unable to dial remote side: %s", err) |
|
} |
|
} |
|
|
|
func TestAuthMethodFallback(t *testing.T) { |
|
var passwordCalled bool |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["rsa"]), |
|
PasswordCallback( |
|
func() (string, error) { |
|
passwordCalled = true |
|
return "WRONG", nil |
|
}), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("unable to dial remote side: %s", err) |
|
} |
|
|
|
if passwordCalled { |
|
t.Errorf("password auth tried before public-key auth.") |
|
} |
|
} |
|
|
|
func TestAuthMethodWrongPassword(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
Password("wrong"), |
|
PublicKeys(testSigners["rsa"]), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("unable to dial remote side: %s", err) |
|
} |
|
} |
|
|
|
func TestAuthMethodKeyboardInteractive(t *testing.T) { |
|
answers := keyboardInteractive(map[string]string{ |
|
"question1": "answer1", |
|
"question2": "answer2", |
|
}) |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
KeyboardInteractive(answers.Challenge), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("unable to dial remote side: %s", err) |
|
} |
|
} |
|
|
|
func TestAuthMethodWrongKeyboardInteractive(t *testing.T) { |
|
answers := keyboardInteractive(map[string]string{ |
|
"question1": "answer1", |
|
"question2": "WRONG", |
|
}) |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
KeyboardInteractive(answers.Challenge), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err == nil { |
|
t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive") |
|
} |
|
} |
|
|
|
// the mock server will only authenticate ssh-rsa keys |
|
func TestAuthMethodInvalidPublicKey(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["dsa"]), |
|
}, |
|
} |
|
|
|
if err := tryAuth(t, config); err == nil { |
|
t.Fatalf("dsa private key should not have authenticated with rsa public key") |
|
} |
|
} |
|
|
|
// the client should authenticate with the second key |
|
func TestAuthMethodRSAandDSA(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["dsa"], testSigners["rsa"]), |
|
}, |
|
} |
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("client could not authenticate with rsa key: %v", err) |
|
} |
|
} |
|
|
|
func TestClientHMAC(t *testing.T) { |
|
for _, mac := range supportedMACs { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["rsa"]), |
|
}, |
|
Config: Config{ |
|
MACs: []string{mac}, |
|
}, |
|
} |
|
if err := tryAuth(t, config); err != nil { |
|
t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err) |
|
} |
|
} |
|
} |
|
|
|
// issue 4285. |
|
func TestClientUnsupportedCipher(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(), |
|
}, |
|
Config: Config{ |
|
Ciphers: []string{"aes128-cbc"}, // not currently supported |
|
}, |
|
} |
|
if err := tryAuth(t, config); err == nil { |
|
t.Errorf("expected no ciphers in common") |
|
} |
|
} |
|
|
|
func TestClientUnsupportedKex(t *testing.T) { |
|
config := &ClientConfig{ |
|
User: "testuser", |
|
Auth: []AuthMethod{ |
|
PublicKeys(), |
|
}, |
|
Config: Config{ |
|
KeyExchanges: []string{"diffie-hellman-group-exchange-sha256"}, // not currently supported |
|
}, |
|
} |
|
if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") { |
|
t.Errorf("got %v, expected 'common algorithm'", err) |
|
} |
|
} |
|
|
|
func TestClientLoginCert(t *testing.T) { |
|
cert := &Certificate{ |
|
Key: testPublicKeys["rsa"], |
|
ValidBefore: CertTimeInfinity, |
|
CertType: UserCert, |
|
} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
certSigner, err := NewCertSigner(cert, testSigners["rsa"]) |
|
if err != nil { |
|
t.Fatalf("NewCertSigner: %v", err) |
|
} |
|
|
|
clientConfig := &ClientConfig{ |
|
User: "user", |
|
} |
|
clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner)) |
|
|
|
t.Log("should succeed") |
|
if err := tryAuth(t, clientConfig); err != nil { |
|
t.Errorf("cert login failed: %v", err) |
|
} |
|
|
|
t.Log("corrupted signature") |
|
cert.Signature.Blob[0]++ |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login passed with corrupted sig") |
|
} |
|
|
|
t.Log("revoked") |
|
cert.Serial = 666 |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("revoked cert login succeeded") |
|
} |
|
cert.Serial = 1 |
|
|
|
t.Log("sign with wrong key") |
|
cert.SignCert(rand.Reader, testSigners["dsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login passed with non-authoritive key") |
|
} |
|
|
|
t.Log("host cert") |
|
cert.CertType = HostCert |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login passed with wrong type") |
|
} |
|
cert.CertType = UserCert |
|
|
|
t.Log("principal specified") |
|
cert.ValidPrincipals = []string{"user"} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err != nil { |
|
t.Errorf("cert login failed: %v", err) |
|
} |
|
|
|
t.Log("wrong principal specified") |
|
cert.ValidPrincipals = []string{"fred"} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login passed with wrong principal") |
|
} |
|
cert.ValidPrincipals = nil |
|
|
|
t.Log("added critical option") |
|
cert.CriticalOptions = map[string]string{"root-access": "yes"} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login passed with unrecognized critical option") |
|
} |
|
|
|
t.Log("allowed source address") |
|
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err != nil { |
|
t.Errorf("cert login with source-address failed: %v", err) |
|
} |
|
|
|
t.Log("disallowed source address") |
|
cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"} |
|
cert.SignCert(rand.Reader, testSigners["ecdsa"]) |
|
if err := tryAuth(t, clientConfig); err == nil { |
|
t.Errorf("cert login with source-address succeeded") |
|
} |
|
} |
|
|
|
func testPermissionsPassing(withPermissions bool, t *testing.T) { |
|
serverConfig := &ServerConfig{ |
|
PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) { |
|
if conn.User() == "nopermissions" { |
|
return nil, nil |
|
} else { |
|
return &Permissions{}, nil |
|
} |
|
}, |
|
} |
|
serverConfig.AddHostKey(testSigners["rsa"]) |
|
|
|
clientConfig := &ClientConfig{ |
|
Auth: []AuthMethod{ |
|
PublicKeys(testSigners["rsa"]), |
|
}, |
|
} |
|
if withPermissions { |
|
clientConfig.User = "permissions" |
|
} else { |
|
clientConfig.User = "nopermissions" |
|
} |
|
|
|
c1, c2, err := netPipe() |
|
if err != nil { |
|
t.Fatalf("netPipe: %v", err) |
|
} |
|
defer c1.Close() |
|
defer c2.Close() |
|
|
|
go NewClientConn(c2, "", clientConfig) |
|
serverConn, err := newServer(c1, serverConfig) |
|
if err != nil { |
|
t.Fatal(err) |
|
} |
|
if p := serverConn.Permissions; (p != nil) != withPermissions { |
|
t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p) |
|
} |
|
} |
|
|
|
func TestPermissionsPassing(t *testing.T) { |
|
testPermissionsPassing(true, t) |
|
} |
|
|
|
func TestNoPermissionsPassing(t *testing.T) { |
|
testPermissionsPassing(false, t) |
|
}
|
|
|