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