| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384 |
- // 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"
- "io"
- "log"
- "net"
- "os"
- "runtime"
- "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. Returns both client and server side errors.
- func tryAuth(t *testing.T, config *ClientConfig) error {
- err, _ := tryAuthBothSides(t, config, nil)
- return err
- }
- // tryAuthWithGSSAPIWithMICConfig 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, gssAPIWithMICConfig *GSSAPIWithMICConfig) (clientError error, serverAuthErrors []error) {
- c1, c2, err := netPipe()
- if err != nil {
- t.Fatalf("netPipe: %v", err)
- }
- defer c1.Close()
- defer c2.Close()
- certChecker := CertChecker{
- IsUserAuthority: 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")
- },
- GSSAPIWithMICConfig: gssAPIWithMICConfig,
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
- serverAuthErrors = append(serverAuthErrors, err)
- }
- go newServer(c1, serverConfig)
- _, _, _, err = NewClientConn(c2, "", config)
- return err, serverAuthErrors
- }
- type loggingAlgorithmSigner struct {
- used []string
- AlgorithmSigner
- }
- func (l *loggingAlgorithmSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
- l.used = append(l.used, "[Sign]")
- return l.AlgorithmSigner.Sign(rand, data)
- }
- func (l *loggingAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
- l.used = append(l.used, algorithm)
- return l.AlgorithmSigner.SignWithAlgorithm(rand, data, algorithm)
- }
- func TestClientAuthPublicKey(t *testing.T) {
- signer := &loggingAlgorithmSigner{AlgorithmSigner: testSigners["rsa"].(AlgorithmSigner)}
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(signer),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, config); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- if len(signer.used) != 1 || signer.used[0] != KeyAlgoRSASHA256 {
- t.Errorf("unexpected Sign/SignWithAlgorithm calls: %q", signer.used)
- }
- }
- // TestClientAuthNoSHA2 tests a ssh-rsa Signer that doesn't implement AlgorithmSigner.
- func TestClientAuthNoSHA2(t *testing.T) {
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(&legacyRSASigner{testSigners["rsa"]}),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, config); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- }
- // TestClientAuthThirdKey checks that the third configured can succeed. If we
- // were to do three attempts for each key (rsa-sha2-256, rsa-sha2-512, ssh-rsa),
- // we'd hit the six maximum attempts before reaching it.
- func TestClientAuthThirdKey(t *testing.T) {
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(testSigners["rsa-openssh-format"],
- testSigners["rsa-openssh-format"], testSigners["rsa"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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
- }),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, config); err != nil {
- t.Fatalf("client could not authenticate with rsa key: %v", err)
- }
- }
- type invalidAlgSigner struct {
- Signer
- }
- func (s *invalidAlgSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
- sig, err := s.Signer.Sign(rand, data)
- if sig != nil {
- sig.Format = "invalid"
- }
- return sig, err
- }
- func TestMethodInvalidAlgorithm(t *testing.T) {
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(&invalidAlgSigner{testSigners["rsa"]}),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- err, serverErrors := tryAuthBothSides(t, config, nil)
- if err == nil {
- t.Fatalf("login succeeded")
- }
- found := false
- want := "algorithm \"invalid\""
- var errStrings []string
- for _, err := range serverErrors {
- found = found || (err != nil && strings.Contains(err.Error(), want))
- errStrings = append(errStrings, err.Error())
- }
- if !found {
- t.Errorf("server got error %q, want substring %q", errStrings, want)
- }
- }
- func TestClientHMAC(t *testing.T) {
- for _, mac := range supportedMACs {
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(testSigners["rsa"]),
- },
- Config: Config{
- MACs: []string{mac},
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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) {
- if os.Getenv("GO_BUILDER_NAME") != "" {
- t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198")
- }
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(),
- },
- Config: Config{
- KeyExchanges: []string{"non-existent-kex"},
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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",
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
- // should succeed
- if err := tryAuth(t, clientConfig); err != nil {
- t.Errorf("cert login failed: %v", err)
- }
- // corrupted signature
- cert.Signature.Blob[0]++
- if err := tryAuth(t, clientConfig); err == nil {
- t.Errorf("cert login passed with corrupted sig")
- }
- // 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
- // sign with wrong key
- cert.SignCert(rand.Reader, testSigners["dsa"])
- if err := tryAuth(t, clientConfig); err == nil {
- t.Errorf("cert login passed with non-authoritative key")
- }
- // 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
- // 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)
- }
- // 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
- // 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")
- }
- // allowed source address
- cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"}
- cert.SignCert(rand.Reader, testSigners["ecdsa"])
- if err := tryAuth(t, clientConfig); err != nil {
- t.Errorf("cert login with source-address failed: %v", err)
- }
- // disallowed source address
- cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::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
- }
- return &Permissions{}, nil
- },
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- clientConfig := &ClientConfig{
- Auth: []AuthMethod{
- PublicKeys(testSigners["rsa"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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)
- }
- func TestRetryableAuth(t *testing.T) {
- n := 0
- passwords := []string{"WRONG1", "WRONG2"}
- config := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- RetryableAuthMethod(PasswordCallback(func() (string, error) {
- p := passwords[n]
- n++
- return p, nil
- }), 2),
- PublicKeys(testSigners["rsa"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, config); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- if n != 2 {
- t.Fatalf("Did not try all passwords")
- }
- }
- func ExampleRetryableAuthMethod() {
- user := "testuser"
- NumberOfPrompts := 3
- // Normally this would be a callback that prompts the user to answer the
- // provided questions
- Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
- return []string{"answer1", "answer2"}, nil
- }
- config := &ClientConfig{
- HostKeyCallback: InsecureIgnoreHostKey(),
- User: user,
- Auth: []AuthMethod{
- RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
- },
- }
- host := "mysshserver"
- netConn, err := net.Dial("tcp", host)
- if err != nil {
- log.Fatal(err)
- }
- sshConn, _, _, err := NewClientConn(netConn, host, config)
- if err != nil {
- log.Fatal(err)
- }
- _ = sshConn
- }
- // Test if username is received on server side when NoClientAuth is used
- func TestClientAuthNone(t *testing.T) {
- user := "testuser"
- serverConfig := &ServerConfig{
- NoClientAuth: true,
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- clientConfig := &ClientConfig{
- User: user,
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- 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.Fatalf("newServer: %v", err)
- }
- if serverConn.User() != user {
- t.Fatalf("server: got %q, want %q", serverConn.User(), user)
- }
- }
- // Test if authentication attempts are limited on server when MaxAuthTries is set
- func TestClientAuthMaxAuthTries(t *testing.T) {
- user := "testuser"
- serverConfig := &ServerConfig{
- MaxAuthTries: 2,
- PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
- if conn.User() == "testuser" && string(pass) == "right" {
- return nil, nil
- }
- return nil, errors.New("password auth failed")
- },
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
- Reason: 2,
- Message: "too many authentication failures",
- })
- for tries := 2; tries < 4; tries++ {
- n := tries
- clientConfig := &ClientConfig{
- User: user,
- Auth: []AuthMethod{
- RetryableAuthMethod(PasswordCallback(func() (string, error) {
- n--
- if n == 0 {
- return "right", nil
- }
- return "wrong", nil
- }), tries),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- c1, c2, err := netPipe()
- if err != nil {
- t.Fatalf("netPipe: %v", err)
- }
- defer c1.Close()
- defer c2.Close()
- errCh := make(chan error, 1)
- go func() {
- _, err := newServer(c1, serverConfig)
- errCh <- err
- }()
- _, _, _, cliErr := NewClientConn(c2, "", clientConfig)
- srvErr := <-errCh
- if tries > serverConfig.MaxAuthTries {
- if cliErr == nil {
- t.Fatalf("client: got no error, want %s", expectedErr)
- } else if cliErr.Error() != expectedErr.Error() {
- t.Fatalf("client: got %s, want %s", err, expectedErr)
- }
- var authErr *ServerAuthError
- if !errors.As(srvErr, &authErr) {
- t.Errorf("expected ServerAuthError, got: %v", srvErr)
- }
- } else {
- if cliErr != nil {
- t.Fatalf("client: got %s, want no error", cliErr)
- }
- }
- }
- }
- // Test if authentication attempts are correctly limited on server
- // when more public keys are provided then MaxAuthTries
- func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
- signers := []Signer{}
- for i := 0; i < 6; i++ {
- signers = append(signers, testSigners["dsa"])
- }
- validConfig := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, validConfig); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
- Reason: 2,
- Message: "too many authentication failures",
- })
- invalidConfig := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- PublicKeys(append(signers, testSigners["rsa"])...),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- if err := tryAuth(t, invalidConfig); err == nil {
- t.Fatalf("client: got no error, want %s", expectedErr)
- } else if err.Error() != expectedErr.Error() {
- // On Windows we can see a WSAECONNABORTED error
- // if the client writes another authentication request
- // before the client goroutine reads the disconnection
- // message. See issue 50805.
- if runtime.GOOS == "windows" && strings.Contains(err.Error(), "wsarecv: An established connection was aborted") {
- // OK.
- } else {
- t.Fatalf("client: got %s, want %s", err, expectedErr)
- }
- }
- }
- // Test whether authentication errors are being properly logged if all
- // authentication methods have been exhausted
- func TestClientAuthErrorList(t *testing.T) {
- publicKeyErr := errors.New("This is an error from PublicKeyCallback")
- clientConfig := &ClientConfig{
- Auth: []AuthMethod{
- PublicKeys(testSigners["rsa"]),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- serverConfig := &ServerConfig{
- PublicKeyCallback: func(_ ConnMetadata, _ PublicKey) (*Permissions, error) {
- return nil, publicKeyErr
- },
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- c1, c2, err := netPipe()
- if err != nil {
- t.Fatalf("netPipe: %v", err)
- }
- defer c1.Close()
- defer c2.Close()
- go NewClientConn(c2, "", clientConfig)
- _, err = newServer(c1, serverConfig)
- if err == nil {
- t.Fatal("newServer: got nil, expected errors")
- }
- authErrs, ok := err.(*ServerAuthError)
- if !ok {
- t.Fatalf("errors: got %T, want *ssh.ServerAuthError", err)
- }
- for i, e := range authErrs.Errors {
- switch i {
- case 0:
- if e != ErrNoAuth {
- t.Fatalf("errors: got error %v, want ErrNoAuth", e)
- }
- case 1:
- if e != publicKeyErr {
- t.Fatalf("errors: got %v, want %v", e, publicKeyErr)
- }
- default:
- t.Fatalf("errors: got %v, expected 2 errors", authErrs.Errors)
- }
- }
- }
- 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)
- }
- }
- }
- }
- func TestCompatibleAlgoAndSignatures(t *testing.T) {
- type testcase struct {
- algo string
- sigFormat string
- compatible bool
- }
- testcases := []*testcase{
- {
- KeyAlgoRSA,
- KeyAlgoRSA,
- true,
- },
- {
- KeyAlgoRSA,
- KeyAlgoRSASHA256,
- true,
- },
- {
- KeyAlgoRSA,
- KeyAlgoRSASHA512,
- true,
- },
- {
- KeyAlgoRSASHA256,
- KeyAlgoRSA,
- true,
- },
- {
- KeyAlgoRSASHA512,
- KeyAlgoRSA,
- true,
- },
- {
- KeyAlgoRSASHA512,
- KeyAlgoRSASHA256,
- true,
- },
- {
- KeyAlgoRSASHA256,
- KeyAlgoRSASHA512,
- true,
- },
- {
- KeyAlgoRSASHA512,
- KeyAlgoRSASHA512,
- true,
- },
- {
- CertAlgoRSAv01,
- KeyAlgoRSA,
- true,
- },
- {
- CertAlgoRSAv01,
- KeyAlgoRSASHA256,
- true,
- },
- {
- CertAlgoRSAv01,
- KeyAlgoRSASHA512,
- true,
- },
- {
- CertAlgoRSASHA256v01,
- KeyAlgoRSASHA512,
- true,
- },
- {
- CertAlgoRSASHA512v01,
- KeyAlgoRSASHA512,
- true,
- },
- {
- CertAlgoRSASHA512v01,
- KeyAlgoRSASHA256,
- true,
- },
- {
- CertAlgoRSASHA256v01,
- CertAlgoRSAv01,
- true,
- },
- {
- CertAlgoRSAv01,
- CertAlgoRSASHA512v01,
- true,
- },
- {
- KeyAlgoECDSA256,
- KeyAlgoRSA,
- false,
- },
- {
- KeyAlgoECDSA256,
- KeyAlgoECDSA521,
- false,
- },
- {
- KeyAlgoECDSA256,
- KeyAlgoECDSA256,
- true,
- },
- {
- KeyAlgoECDSA256,
- KeyAlgoED25519,
- false,
- },
- {
- KeyAlgoED25519,
- KeyAlgoED25519,
- true,
- },
- }
- for _, c := range testcases {
- if isAlgoCompatible(c.algo, c.sigFormat) != c.compatible {
- t.Errorf("algorithm %q, signature format %q, expected compatible to be %t", c.algo, c.sigFormat, c.compatible)
- }
- }
- }
- func TestPickSignatureAlgorithm(t *testing.T) {
- type testcase struct {
- name string
- extensions map[string][]byte
- }
- cases := []testcase{
- {
- name: "server with empty server-sig-algs",
- extensions: map[string][]byte{
- "server-sig-algs": []byte(``),
- },
- },
- {
- name: "server with no server-sig-algs",
- extensions: nil,
- },
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- signer, ok := testSigners["rsa"].(MultiAlgorithmSigner)
- if !ok {
- t.Fatalf("rsa test signer does not implement the MultiAlgorithmSigner interface")
- }
- // The signer supports the public key algorithm which is then returned.
- _, algo, err := pickSignatureAlgorithm(signer, c.extensions)
- if err != nil {
- t.Fatalf("got %v, want no error", err)
- }
- if algo != signer.PublicKey().Type() {
- t.Fatalf("got algo %q, want %q", algo, signer.PublicKey().Type())
- }
- // Test a signer that uses a certificate algorithm as the public key
- // type.
- cert := &Certificate{
- CertType: UserCert,
- Key: signer.PublicKey(),
- }
- cert.SignCert(rand.Reader, signer)
- certSigner, err := NewCertSigner(cert, signer)
- if err != nil {
- t.Fatalf("error generating cert signer: %v", err)
- }
- // The signer supports the public key algorithm and the
- // public key format is a certificate type so the cerificate
- // algorithm matching the key format must be returned
- _, algo, err = pickSignatureAlgorithm(certSigner, c.extensions)
- if err != nil {
- t.Fatalf("got %v, want no error", err)
- }
- if algo != certSigner.PublicKey().Type() {
- t.Fatalf("got algo %q, want %q", algo, certSigner.PublicKey().Type())
- }
- signer, err = NewSignerWithAlgorithms(signer.(AlgorithmSigner), []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256})
- if err != nil {
- t.Fatalf("unable to create signer with algorithms: %v", err)
- }
- // The signer does not support the public key algorithm so an error
- // is returned.
- _, _, err = pickSignatureAlgorithm(signer, c.extensions)
- if err == nil {
- t.Fatal("got no error, no common public key signature algorithm error expected")
- }
- })
- }
- }
- // configurablePublicKeyCallback is a public key callback that allows to
- // configure the signature algorithm and format. This way we can emulate the
- // behavior of buggy clients.
- type configurablePublicKeyCallback struct {
- signer AlgorithmSigner
- signatureAlgo string
- signatureFormat string
- }
- func (cb configurablePublicKeyCallback) method() string {
- return "publickey"
- }
- func (cb configurablePublicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
- pub := cb.signer.PublicKey()
- ok, err := validateKey(pub, cb.signatureAlgo, user, c)
- if err != nil {
- return authFailure, nil, err
- }
- if !ok {
- return authFailure, nil, fmt.Errorf("invalid public key")
- }
- pubKey := pub.Marshal()
- data := buildDataSignedForAuth(session, userAuthRequestMsg{
- User: user,
- Service: serviceSSH,
- Method: cb.method(),
- }, cb.signatureAlgo, pubKey)
- sign, err := cb.signer.SignWithAlgorithm(rand, data, underlyingAlgo(cb.signatureFormat))
- if err != nil {
- return authFailure, nil, err
- }
- s := Marshal(sign)
- sig := make([]byte, stringLength(len(s)))
- marshalString(sig, s)
- msg := publickeyAuthMsg{
- User: user,
- Service: serviceSSH,
- Method: cb.method(),
- HasSig: true,
- Algoname: cb.signatureAlgo,
- PubKey: pubKey,
- Sig: sig,
- }
- p := Marshal(&msg)
- if err := c.writePacket(p); err != nil {
- return authFailure, nil, err
- }
- var success authResult
- success, methods, err := handleAuthResponse(c)
- if err != nil {
- return authFailure, nil, err
- }
- if success == authSuccess || !contains(methods, cb.method()) {
- return success, methods, err
- }
- return authFailure, methods, nil
- }
- func TestPublicKeyAndAlgoCompatibility(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",
- HostKeyCallback: InsecureIgnoreHostKey(),
- Auth: []AuthMethod{
- configurablePublicKeyCallback{
- signer: certSigner.(AlgorithmSigner),
- signatureAlgo: KeyAlgoRSASHA256,
- signatureFormat: KeyAlgoRSASHA256,
- },
- },
- }
- if err := tryAuth(t, clientConfig); err == nil {
- t.Error("cert login passed with incompatible public key type and algorithm")
- }
- }
- func TestClientAuthGPGAgentCompat(t *testing.T) {
- clientConfig := &ClientConfig{
- User: "testuser",
- HostKeyCallback: InsecureIgnoreHostKey(),
- Auth: []AuthMethod{
- // algorithm rsa-sha2-512 and signature format ssh-rsa.
- configurablePublicKeyCallback{
- signer: testSigners["rsa"].(AlgorithmSigner),
- signatureAlgo: KeyAlgoRSASHA512,
- signatureFormat: KeyAlgoRSA,
- },
- },
- }
- if err := tryAuth(t, clientConfig); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- }
- func TestCertAuthOpenSSHCompat(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",
- HostKeyCallback: InsecureIgnoreHostKey(),
- Auth: []AuthMethod{
- // algorithm ssh-rsa-cert-v01@openssh.com and signature format
- // rsa-sha2-256.
- configurablePublicKeyCallback{
- signer: certSigner.(AlgorithmSigner),
- signatureAlgo: CertAlgoRSAv01,
- signatureFormat: KeyAlgoRSASHA256,
- },
- },
- }
- if err := tryAuth(t, clientConfig); err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
- }
- }
- func TestKeyboardInteractiveAuthEarlyFail(t *testing.T) {
- const maxAuthTries = 2
- c1, c2, err := netPipe()
- if err != nil {
- t.Fatalf("netPipe: %v", err)
- }
- defer c1.Close()
- defer c2.Close()
- // Start testserver
- serverConfig := &ServerConfig{
- MaxAuthTries: maxAuthTries,
- KeyboardInteractiveCallback: func(c ConnMetadata,
- client KeyboardInteractiveChallenge) (*Permissions, error) {
- // Fail keyboard-interactive authentication early before
- // any prompt is sent to client.
- return nil, errors.New("keyboard-interactive auth failed")
- },
- PasswordCallback: func(c ConnMetadata,
- pass []byte) (*Permissions, error) {
- if string(pass) == clientPassword {
- return nil, nil
- }
- return nil, errors.New("password auth failed")
- },
- }
- serverConfig.AddHostKey(testSigners["rsa"])
- serverDone := make(chan struct{})
- go func() {
- defer func() { serverDone <- struct{}{} }()
- conn, chans, reqs, err := NewServerConn(c2, serverConfig)
- if err != nil {
- return
- }
- _ = conn.Close()
- discarderDone := make(chan struct{})
- go func() {
- defer func() { discarderDone <- struct{}{} }()
- DiscardRequests(reqs)
- }()
- for newChannel := range chans {
- newChannel.Reject(Prohibited,
- "testserver not accepting requests")
- }
- <-discarderDone
- }()
- // Connect to testserver, expect KeyboardInteractive() to be not called,
- // PasswordCallback() to be called and connection to succeed.
- passwordCallbackCalled := false
- clientConfig := &ClientConfig{
- User: "testuser",
- Auth: []AuthMethod{
- RetryableAuthMethod(KeyboardInteractive(func(name,
- instruction string, questions []string,
- echos []bool) ([]string, error) {
- t.Errorf("unexpected call to KeyboardInteractive()")
- return []string{clientPassword}, nil
- }), maxAuthTries),
- RetryableAuthMethod(PasswordCallback(func() (secret string,
- err error) {
- t.Logf("PasswordCallback()")
- passwordCallbackCalled = true
- return clientPassword, nil
- }), maxAuthTries),
- },
- HostKeyCallback: InsecureIgnoreHostKey(),
- }
- conn, _, _, err := NewClientConn(c1, "", clientConfig)
- if err != nil {
- t.Errorf("unexpected NewClientConn() error: %v", err)
- }
- if conn != nil {
- conn.Close()
- }
- // Wait for server to finish.
- <-serverDone
- if !passwordCallbackCalled {
- t.Errorf("expected PasswordCallback() to be called")
- }
- }
|