| // 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) |
| } |