diff --git a/Gopkg.lock b/Gopkg.lock index d98b1f09..09ed3651 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -13,6 +13,12 @@ packages = [".","util/strutil"] revision = "d0567a9d84a1c40dd7568115ea66f4887bf57b33" +[[projects]] + branch = "master" + name = "github.com/mars9/crypt" + packages = ["."] + revision = "65899cf653ff022fe5c7fe504b439feed9e7e0fc" + [[projects]] name = "github.com/mattn/go-isatty" packages = ["."] @@ -40,8 +46,8 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3" + packages = ["pbkdf2","scrypt","ssh/terminal"] + revision = "541b9d50ad47e36efd8fb423e938e59ff1691f68" [[projects]] branch = "master" @@ -58,6 +64,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b5f57442f43856cf17500cec2a357c4ea34e5f9eae4298954f62765111b0d5a9" + inputs-digest = "b7969c854fd8997ea62c1a9ed968e5eeac091a3fc5650cca64e99e888a0156db" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/mars9/crypt/.gitignore b/vendor/github.com/mars9/crypt/.gitignore new file mode 100644 index 00000000..fd88b64f --- /dev/null +++ b/vendor/github.com/mars9/crypt/.gitignore @@ -0,0 +1 @@ +crypt/crypt diff --git a/vendor/github.com/mars9/crypt/LICENSE b/vendor/github.com/mars9/crypt/LICENSE new file mode 100644 index 00000000..2135192f --- /dev/null +++ b/vendor/github.com/mars9/crypt/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Markus Sonderegger + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/mars9/crypt/README.md b/vendor/github.com/mars9/crypt/README.md new file mode 100644 index 00000000..6ace8e0d --- /dev/null +++ b/vendor/github.com/mars9/crypt/README.md @@ -0,0 +1,4 @@ +# crypt + +Package crypt provides password-based encryption and decryption of +data streams. diff --git a/vendor/github.com/mars9/crypt/crypt.go b/vendor/github.com/mars9/crypt/crypt.go new file mode 100644 index 00000000..439a0df7 --- /dev/null +++ b/vendor/github.com/mars9/crypt/crypt.go @@ -0,0 +1,165 @@ +// Package crypt provides password-based encryption and decryption of +// data streams. +package crypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "errors" + "hash" + "io" +) + +const ( + blockSize = aes.BlockSize // AES block size + version = 1 +) + +// Crypter encrypt/decrypts with AES (Rijndael) in cipher block counter +// mode (CTR) and authenticate with HMAC-SHA. +type Crypter struct { + HashFunc func() hash.Hash + HashSize int + Key Key + BufSize int +} + +func (c *Crypter) encHeader(salt, iv, hmacKey []byte) []byte { + keySize := c.Key.Size() + headerSize := 1 + keySize + blockSize + c.HashSize + + b := make([]byte, headerSize) + b[0] = version + copy(b[1:1+keySize], salt) + copy(b[1+keySize:1+keySize+blockSize], iv) + + mac := hmac.New(c.HashFunc, hmacKey) + mac.Write(b[:1+keySize+blockSize]) + copy(b[1+keySize+blockSize:], mac.Sum(nil)) + return b +} + +func (c *Crypter) bufSize() int { + if c.BufSize == 0 { + return 2 * 1024 * 1024 + } + return c.BufSize +} + +func (c *Crypter) decHeader(b []byte) ([]byte, []byte, error) { + if b[0] != version { + return nil, nil, errors.New("malformed encrypted packet") + } + + keySize := c.Key.Size() + salt := b[1 : 1+keySize] + iv := b[1+keySize : 1+keySize+blockSize] + return salt, iv, nil +} + +// Encrypt encrypts from src until either EOF is reached on src or an +// error occurs. A successful Encrypt returns err == nil, not err == EOF. +func (c *Crypter) Encrypt(dst io.Writer, src io.Reader) (err error) { + salt := make([]byte, c.Key.Size()) + if _, err := rand.Read(salt); err != nil { + return err + } + iv := make([]byte, blockSize) + if _, err := rand.Read(iv); err != nil { + return err + } + + aesKey, hmacKey := c.Key.Derive(salt) + header := c.encHeader(salt, iv, hmacKey) + if _, err := dst.Write(header); err != nil { + return err + } + mac := hmac.New(c.HashFunc, hmacKey) + mac.Write(header) + + block, err := aes.NewCipher(aesKey) + if err != nil { + return err + } + + stream := cipher.NewCTR(block, iv) + + buf := make([]byte, c.bufSize()) + n := 0 + for { + n, err = src.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + + mac.Write(buf[:n]) + stream.XORKeyStream(buf[:n], buf[:n]) + if _, err = dst.Write(buf[:n]); err != nil { + return err + } + if _, err = dst.Write(mac.Sum(nil)); err != nil { + return err + } + } + return nil +} + +// Decrypt decrypts from src until either EOF is reached on src or an +// error occurs. A successful Decrypt returns err == nil, not err == EOF. +func (c *Crypter) Decrypt(dst io.Writer, src io.Reader) (err error) { + keySize := c.Key.Size() + headerSize := 1 + keySize + blockSize + c.HashSize + + header := make([]byte, headerSize) + if _, err = src.Read(header); err != nil { + return err + } + + salt, iv, err := c.decHeader(header) + if err != nil { + return err + } + aesKey, hmacKey := c.Key.Derive(salt) + + mac := hmac.New(c.HashFunc, hmacKey) + mac.Write(header[:1+keySize+blockSize]) + + if !bytes.Equal(header[1+keySize+blockSize:], mac.Sum(nil)) { + return errors.New("cannot authenticate header") + } + mac.Write(header[1+keySize+blockSize:]) + + block, err := aes.NewCipher(aesKey) + if err != nil { + return err + } + + stream := cipher.NewCTR(block, iv) + buf := make([]byte, c.bufSize()+c.HashSize) + n := 0 + for { + n, err = src.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + + stream.XORKeyStream(buf[:n-c.HashSize], buf[:n-c.HashSize]) + mac.Write(buf[:n-c.HashSize]) + if !bytes.Equal(buf[n-c.HashSize:n], mac.Sum(nil)) { + return errors.New("cannot authenticate packet") + } + if _, err = dst.Write(buf[:n-c.HashSize]); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/mars9/crypt/crypt_test.go b/vendor/github.com/mars9/crypt/crypt_test.go new file mode 100644 index 00000000..e8ec2337 --- /dev/null +++ b/vendor/github.com/mars9/crypt/crypt_test.go @@ -0,0 +1,82 @@ +package crypt + +import ( + "bytes" + "crypto/sha1" + "io/ioutil" + "os" + "testing" +) + +var plain = [][]byte{ + []byte("Nö, ich trinke keinen Tee, ich bin Atheist. --- Helge Schneider"), + []byte("I wish these damn scientists would leave intelligence to the experts. --- Gen. Richard Stillwell (CIA)"), + []byte("I want to die peacefully in my sleep like my grandfather, not screaming in terror like his passengers. --- Charlie Hall"), + []byte("NOTE 3: Each bit has the value either ZERO or ONE. --- ECMA-035 spec"), + []byte("Writing about music is like dancing about architecture. --- Frank Zappa"), + []byte("If you want to go somewhere, goto is the best way to get there. --- K Thompson"), +} + +func TestEncryptDecrypt(t *testing.T) { + enc := bytes.NewBuffer(nil) + dec := bytes.NewBuffer(nil) + password := []byte("test password") + c := &Crypter{ + HashFunc: sha1.New, + HashSize: sha1.Size, + Key: NewPbkdf2Key(password, 32), + } + defer c.Key.Reset() + + for _, src := range plain { + enc.Reset() + dec.Reset() + err := c.Encrypt(enc, bytes.NewReader(src)) + if err != nil { + t.Fatal(err) + } + err = c.Decrypt(dec, enc) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(dec.Bytes(), src) != 0 { + t.Errorf("encrypt/decrypt error: want %q, got %q", string(src), string(dec.Bytes())) + } + } +} + +func TestEncryptDecrypt1(t *testing.T) { + f, err := os.Open("crypt_test.go") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + encBuf := bytes.NewBuffer(nil) + password := []byte("test password") + c := &Crypter{ + HashFunc: sha1.New, + HashSize: sha1.Size, + Key: NewPbkdf2Key(password, 32), + } + defer c.Key.Reset() + + err = c.Encrypt(encBuf, f) + if err != nil { + t.Fatal(err) + } + + decBuf := bytes.NewBuffer(nil) + err = c.Decrypt(decBuf, encBuf) + if err != nil { + t.Fatal(err) + } + + src, err := ioutil.ReadFile("crypt_test.go") + if err != nil { + t.Fatal(err) + } + if bytes.Compare(decBuf.Bytes(), src) != 0 { + t.Errorf("encrypt/decrypt file error: crypt_test.go") + } +} diff --git a/vendor/github.com/mars9/crypt/crypto/main.go b/vendor/github.com/mars9/crypt/crypto/main.go new file mode 100644 index 00000000..4ae3445c --- /dev/null +++ b/vendor/github.com/mars9/crypt/crypto/main.go @@ -0,0 +1,170 @@ +package main + +import ( + "bytes" + "crypto/sha1" + "flag" + "fmt" + "os" + "runtime" + + "github.com/mars9/crypt" + "github.com/mars9/keyring" + "github.com/mars9/passwd" +) + +var ( + prompt = flag.Bool("p", false, "prompt to enter a passphrase") + decrypt = flag.Bool("d", false, "decrypt infile to oufile") + service = flag.String("s", "go-crypto", "keyring service name") + username = flag.String("u", os.Getenv("USER"), "keyring username") + initKeyring = flag.Bool("i", false, "intialize keyring") +) + +func passphrase() ([]byte, error) { + if *prompt { + password, err := passwd.Get("Enter passphrase: ") + if err != nil { + return nil, fmt.Errorf("get passphrase: %v\n", err) + } + + if !*decrypt { + confirm, err := passwd.Get("Confirm passphrase: ") + if err != nil { + return nil, fmt.Errorf("get passphrase: %v\n", err) + } + if !bytes.Equal(password, confirm) { + return nil, fmt.Errorf("Passphrase mismatch, try again.") + } + } + return password, nil + } + + ring, err := keyring.New() + if err != nil { + return nil, err + } + return ring.Get(*service, *username) +} + +func initialize() error { + password, err := passwd.Get("Enter passphrase: ") + if err != nil { + return fmt.Errorf("get passphrase: %v\n", err) + } + + confirm, err := passwd.Get("Confirm passphrase: ") + if err != nil { + return fmt.Errorf("get passphrase: %v\n", err) + } + if !bytes.Equal(password, confirm) { + return fmt.Errorf("Passphrase mismatch, try again.") + } + + ring, err := keyring.New() + if err != nil { + return err + } + return ring.Set(*service, *username, password) +} + +func main() { + flag.Usage = usage + flag.Parse() + narg := flag.NArg() + if narg > 2 { + usage() + } + if runtime.GOOS == "windows" && narg == 0 { + usage() + } + + if *initKeyring { + if err := initialize(); err != nil { + fmt.Fprintf(os.Stderr, "initialize keyring: %v", err) + os.Exit(1) + } + os.Exit(0) + } + + password, err := passphrase() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(3) + } + defer func() { + for i := range password { + password[i] = 0 + } + }() + + in := os.Stdin + out := os.Stdout + if narg > 0 { + in, err = os.Open(flag.Arg(0)) + if err != nil { + fmt.Fprintf(os.Stderr, "open %s: %v\n", flag.Arg(0), err) + os.Exit(1) + } + defer in.Close() + + if narg == 2 { + out, err = os.Create(flag.Arg(1)) + if err != nil { + fmt.Fprintf(os.Stderr, "create %s: %v\n", flag.Arg(1), err) + os.Exit(1) + } + defer func() { + if err := out.Sync(); err != nil { + fmt.Fprintf(os.Stderr, "sync %s: %v\n", flag.Arg(1), err) + os.Exit(1) + } + if err := out.Close(); err != nil { + fmt.Fprintf(os.Stderr, "sync %s: %v\n", flag.Arg(1), err) + os.Exit(1) + } + }() + } + } + + c := &crypt.Crypter{ + HashFunc: sha1.New, + HashSize: sha1.Size, + Key: crypt.NewPbkdf2Key(password, 32), + } + + if !*decrypt { + if err := c.Encrypt(out, in); err != nil { + fmt.Fprintf(os.Stderr, "encrypt: %v\n", err) + os.Exit(1) + } + } else { + if err := c.Decrypt(out, in); err != nil { + fmt.Fprintf(os.Stderr, "decrypt: %v\n", err) + os.Exit(1) + } + } +} + +func usage() { + if runtime.GOOS == "windows" { + fmt.Fprintf(os.Stderr, "Usage: %s [options] infile [outfile]\n", os.Args[0]) + } else { + fmt.Fprintf(os.Stderr, "Usage: %s [options] [infile] [[outfile]]\n", os.Args[0]) + } + fmt.Fprint(os.Stderr, usageMsg) + fmt.Fprintf(os.Stderr, "\nOptions:\n") + flag.PrintDefaults() + os.Exit(2) +} + +const usageMsg = ` +Files are encrypted with AES (Rijndael) in cipher block counter mode +(CTR) and authenticate with HMAC-SHA. Encryption and HMAC keys are +derived from passphrase using PBKDF2. + +If outfile is not specified, the de-/encrypted data is written to the +standard output and if infile is not specified, the de-/encrypted data +is read from standard input (reading standard input is not available +on windows). +` diff --git a/vendor/github.com/mars9/crypt/key.go b/vendor/github.com/mars9/crypt/key.go new file mode 100644 index 00000000..917ab05f --- /dev/null +++ b/vendor/github.com/mars9/crypt/key.go @@ -0,0 +1,79 @@ +package crypt + +import ( + "crypto/sha1" + + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/scrypt" +) + +// Key defines the key derivation function interface. +type Key interface { + // Derive returns the AES key and HMAC-SHA key, for the given password, + // salt combination. + Derive(salt []byte) (aesKey, hmacKey []byte) + + // Size returns the key-size. Key-size should either 16, 24, or 32 to + // select AES-128, AES-192, or AES-256. + Size() int + + // Reset resets/flushes the key. + Reset() +} + +type pbkdf2Key struct { + password []byte + size int +} + +// NewPbkdf2Key returns the key derivation function PBKDF2 as defined in +// RFC 2898. +func NewPbkdf2Key(password []byte, size int) Key { + return pbkdf2Key{password: password, size: size} +} + +func (k pbkdf2Key) Derive(salt []byte) (aesKey, hmacKey []byte) { + key := pbkdf2.Key(k.password, salt, 4096, 2*k.size, sha1.New) + aesKey = key[:k.size] + hmacKey = key[k.size:] + return aesKey, hmacKey +} + +func (k pbkdf2Key) Size() int { return k.size } + +func (k pbkdf2Key) Reset() { + for i := range k.password { + k.password[i] = 0 + } +} + +type scryptKey struct { + password []byte + size int +} + +// NewScryptKey returns the scrypt key derivation function as defined in +// Colin Percival's paper "Stronger Key Derivation via Sequential +// Memory-Hard Functions". +func NewScryptKey(password []byte, size int) Key { + return scryptKey{password: password, size: size} +} + +func (k scryptKey) Derive(salt []byte) (aesKey, hmacKey []byte) { + key, err := scrypt.Key(k.password, salt, 16384, 8, 1, 2*k.size) + if err != nil { + panic(err) + } + + aesKey = key[:k.size] + hmacKey = key[k.size:] + return aesKey, hmacKey +} + +func (k scryptKey) Size() int { return k.size } + +func (k scryptKey) Reset() { + for i := range k.password { + k.password[i] = 0 + } +} diff --git a/vendor/golang.org/x/crypto/acme/acme.go b/vendor/golang.org/x/crypto/acme/acme.go index e8388b08..fa9c4b39 100644 --- a/vendor/golang.org/x/crypto/acme/acme.go +++ b/vendor/golang.org/x/crypto/acme/acme.go @@ -142,7 +142,7 @@ func (c *Client) Discover(ctx context.Context) (Directory, error) { // // In the case where CA server does not provide the issued certificate in the response, // CreateCert will poll certURL using c.FetchCert, which will result in additional round-trips. -// In such scenario the caller can cancel the polling with ctx. +// In such a scenario, the caller can cancel the polling with ctx. // // CreateCert returns an error if the CA's response or chain was unreasonably large. // Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features. @@ -257,7 +257,7 @@ func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, func AcceptTOS(tosURL string) bool { return true } // Register creates a new account registration by following the "new-reg" flow. -// It returns registered account. The account is not modified. +// It returns the registered account. The account is not modified. // // The registration may require the caller to agree to the CA's Terms of Service (TOS). // If so, and the account has not indicated the acceptance of the terms (see Account for details), @@ -995,6 +995,7 @@ func keyAuth(pub crypto.PublicKey, token string) (string, error) { // tlsChallengeCert creates a temporary certificate for TLS-SNI challenges // with the given SANs and auto-generated public/private key pair. +// The Subject Common Name is set to the first SAN to aid debugging. // To create a cert with a custom key pair, specify WithKey option. func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { var ( @@ -1033,6 +1034,9 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { } } tmpl.DNSNames = san + if len(san) > 0 { + tmpl.Subject.CommonName = san[0] + } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) if err != nil { diff --git a/vendor/golang.org/x/crypto/acme/acme_test.go b/vendor/golang.org/x/crypto/acme/acme_test.go index 14832de4..b44af595 100644 --- a/vendor/golang.org/x/crypto/acme/acme_test.go +++ b/vendor/golang.org/x/crypto/acme/acme_test.go @@ -1186,6 +1186,9 @@ func TestTLSSNI01ChallengeCert(t *testing.T) { if cert.DNSNames[0] != name { t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name) } + if cn := cert.Subject.CommonName; cn != san { + t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san) + } } func TestTLSSNI02ChallengeCert(t *testing.T) { @@ -1219,6 +1222,9 @@ func TestTLSSNI02ChallengeCert(t *testing.T) { if i >= len(cert.DNSNames) || cert.DNSNames[i] != name { t.Errorf("%v doesn't have %q", cert.DNSNames, name) } + if cn := cert.Subject.CommonName; cn != sanA { + t.Errorf("CommonName = %q; want %q", cn, sanA) + } } func TestTLSChallengeCertOpt(t *testing.T) { diff --git a/vendor/golang.org/x/crypto/acme/autocert/autocert.go b/vendor/golang.org/x/crypto/acme/autocert/autocert.go index b1010201..94edba98 100644 --- a/vendor/golang.org/x/crypto/acme/autocert/autocert.go +++ b/vendor/golang.org/x/crypto/acme/autocert/autocert.go @@ -371,7 +371,7 @@ func (m *Manager) createCert(ctx context.Context, domain string) (*tls.Certifica // We are the first; state is locked. // Unblock the readers when domain ownership is verified - // and the we got the cert or the process failed. + // and we got the cert or the process failed. defer state.Unlock() state.locked = false @@ -439,7 +439,7 @@ func (m *Manager) certState(domain string) (*certState, error) { return state, nil } -// authorizedCert starts domain ownership verification process and requests a new cert upon success. +// authorizedCert starts the domain ownership verification process and requests a new cert upon success. // The key argument is the certificate private key. func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, domain string) (der [][]byte, leaf *x509.Certificate, err error) { if err := m.verify(ctx, domain); err != nil { diff --git a/vendor/golang.org/x/crypto/ssh/client.go b/vendor/golang.org/x/crypto/ssh/client.go index a7e3263b..6fd19945 100644 --- a/vendor/golang.org/x/crypto/ssh/client.go +++ b/vendor/golang.org/x/crypto/ssh/client.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net" + "os" "sync" "time" ) @@ -187,6 +188,10 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) { // net.Conn underlying the the SSH connection. type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error +// BannerCallback is the function type used for treat the banner sent by +// the server. A BannerCallback receives the message sent by the remote server. +type BannerCallback func(message string) error + // A ClientConfig structure is used to configure a Client. It must not be // modified after having been passed to an SSH function. type ClientConfig struct { @@ -209,6 +214,12 @@ type ClientConfig struct { // FixedHostKey can be used for simplistic host key checks. HostKeyCallback HostKeyCallback + // BannerCallback is called during the SSH dance to display a custom + // server's message. The client configuration can supply this callback to + // handle it as wished. The function BannerDisplayStderr can be used for + // simplistic display on Stderr. + BannerCallback BannerCallback + // ClientVersion contains the version identification string that will // be used for the connection. If empty, a reasonable default is used. ClientVersion string @@ -255,3 +266,13 @@ func FixedHostKey(key PublicKey) HostKeyCallback { hk := &fixedHostKey{key} return hk.check } + +// BannerDisplayStderr returns a function that can be used for +// ClientConfig.BannerCallback to display banners on os.Stderr. +func BannerDisplayStderr() BannerCallback { + return func(banner string) error { + _, err := os.Stderr.WriteString(banner) + + return err + } +} diff --git a/vendor/golang.org/x/crypto/ssh/client_auth.go b/vendor/golang.org/x/crypto/ssh/client_auth.go index 3acd8d49..a1252cb9 100644 --- a/vendor/golang.org/x/crypto/ssh/client_auth.go +++ b/vendor/golang.org/x/crypto/ssh/client_auth.go @@ -283,7 +283,9 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { } switch packet[0] { case msgUserAuthBanner: - // TODO(gpaul): add callback to present the banner to the user + if err := handleBannerResponse(c, packet); err != nil { + return false, err + } case msgUserAuthPubKeyOk: var msg userAuthPubKeyOkMsg if err := Unmarshal(packet, &msg); err != nil { @@ -325,7 +327,9 @@ func handleAuthResponse(c packetConn) (bool, []string, error) { switch packet[0] { case msgUserAuthBanner: - // TODO: add callback to present the banner to the user + if err := handleBannerResponse(c, packet); err != nil { + return false, nil, err + } case msgUserAuthFailure: var msg userAuthFailureMsg if err := Unmarshal(packet, &msg); err != nil { @@ -340,6 +344,24 @@ func handleAuthResponse(c packetConn) (bool, []string, error) { } } +func handleBannerResponse(c packetConn, packet []byte) error { + var msg userAuthBannerMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + transport, ok := c.(*handshakeTransport) + if !ok { + return nil + } + + if transport.bannerCallback != nil { + return transport.bannerCallback(msg.Message) + } + + return nil +} + // KeyboardInteractiveChallenge should print questions, optionally // disabling echoing (e.g. for passwords), and return all the answers. // Challenge may be called multiple times in a single session. After @@ -385,7 +407,9 @@ func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packe // like handleAuthResponse, but with less options. switch packet[0] { case msgUserAuthBanner: - // TODO: Print banners during userauth. + if err := handleBannerResponse(c, packet); err != nil { + return false, nil, err + } continue case msgUserAuthInfoRequest: // OK diff --git a/vendor/golang.org/x/crypto/ssh/client_test.go b/vendor/golang.org/x/crypto/ssh/client_test.go index ccf56074..f751eb6c 100644 --- a/vendor/golang.org/x/crypto/ssh/client_test.go +++ b/vendor/golang.org/x/crypto/ssh/client_test.go @@ -79,3 +79,40 @@ func TestHostKeyCheck(t *testing.T) { } } } +func TestBannerCallback(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + serverConf := &ServerConfig{ + NoClientAuth: true, + BannerCallback: func(conn ConnMetadata) string { + return "Hello World" + }, + } + serverConf.AddHostKey(testSigners["rsa"]) + go NewServerConn(c1, serverConf) + + var receivedBanner string + clientConf := ClientConfig{ + User: "user", + HostKeyCallback: InsecureIgnoreHostKey(), + BannerCallback: func(message string) error { + receivedBanner = message + return nil + }, + } + + _, _, _, err = NewClientConn(c2, "", &clientConf) + if err != nil { + t.Fatal(err) + } + + expected := "Hello World" + if receivedBanner != expected { + t.Fatalf("got %s; want %s", receivedBanner, expected) + } +} diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go index 932ce839..4f7912ec 100644 --- a/vendor/golang.org/x/crypto/ssh/handshake.go +++ b/vendor/golang.org/x/crypto/ssh/handshake.go @@ -78,6 +78,11 @@ type handshakeTransport struct { dialAddress string remoteAddr net.Addr + // bannerCallback is non-empty if we are the client and it has been set in + // ClientConfig. In that case it is called during the user authentication + // dance to handle a custom server's message. + bannerCallback BannerCallback + // Algorithms agreed in the last key exchange. algorithms *algorithms @@ -120,6 +125,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt t.dialAddress = dialAddr t.remoteAddr = addr t.hostKeyCallback = config.HostKeyCallback + t.bannerCallback = config.BannerCallback if config.HostKeyAlgorithms != nil { t.hostKeyAlgorithms = config.HostKeyAlgorithms } else { diff --git a/vendor/golang.org/x/crypto/ssh/messages.go b/vendor/golang.org/x/crypto/ssh/messages.go index e6ecd3af..92f3810e 100644 --- a/vendor/golang.org/x/crypto/ssh/messages.go +++ b/vendor/golang.org/x/crypto/ssh/messages.go @@ -23,10 +23,6 @@ const ( msgUnimplemented = 3 msgDebug = 4 msgNewKeys = 21 - - // Standard authentication messages - msgUserAuthSuccess = 52 - msgUserAuthBanner = 53 ) // SSH messages: @@ -137,6 +133,16 @@ type userAuthFailureMsg struct { PartialSuccess bool } +// See RFC 4252, section 5.1 +const msgUserAuthSuccess = 52 + +// See RFC 4252, section 5.4 +const msgUserAuthBanner = 53 + +type userAuthBannerMsg struct { + Message string `sshtype:"53"` +} + // See RFC 4256, section 3.2 const msgUserAuthInfoRequest = 60 const msgUserAuthInfoResponse = 61 diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go index 8a78b7ca..148d2cb2 100644 --- a/vendor/golang.org/x/crypto/ssh/server.go +++ b/vendor/golang.org/x/crypto/ssh/server.go @@ -95,6 +95,10 @@ type ServerConfig struct { // Note that RFC 4253 section 4.2 requires that this string start with // "SSH-2.0-". ServerVersion string + + // BannerCallback, if present, is called and the return string is sent to + // the client after key exchange completed but before authentication. + BannerCallback func(conn ConnMetadata) string } // AddHostKey adds a private key as a host key. If an existing host @@ -343,6 +347,19 @@ userAuthLoop: } s.user = userAuthReq.User + + if authFailures == 0 && config.BannerCallback != nil { + msg := config.BannerCallback(s) + if msg != "" { + bannerMsg := &userAuthBannerMsg{ + Message: msg, + } + if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil { + return nil, err + } + } + } + perms = nil authErr := errors.New("no auth passed yet")