| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284 |
- // 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
- }
- // 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, 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()
- go newServer(c1, serverConfig)
- _, _, _, err = NewClientConn(c2, "", clientConfig)
- if tries > 2 {
- if err == nil {
- t.Fatalf("client: got no error, want %s", expectedErr)
- } else if err.Error() != expectedErr.Error() {
- t.Fatalf("client: got %s, want %s", err, expectedErr)
- }
- } else {
- if err != nil {
- t.Fatalf("client: got %s, want no error", err)
- }
- }
- }
- }
- // 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)
- }
- }
|