| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676 |
- // 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 (
- "crypto"
- "crypto/rand"
- "fmt"
- "io"
- "math"
- "slices"
- "sync"
- _ "crypto/sha1"
- _ "crypto/sha256"
- _ "crypto/sha512"
- )
- // These are string constants in the SSH protocol.
- const (
- compressionNone = "none"
- serviceUserAuth = "ssh-userauth"
- serviceSSH = "ssh-connection"
- )
- // The ciphers currently or previously implemented by this library, to use in
- // [Config.Ciphers]. For a list, see the [Algorithms.Ciphers] returned by
- // [SupportedAlgorithms] or [InsecureAlgorithms].
- const (
- CipherAES128GCM = "aes128-gcm@openssh.com"
- CipherAES256GCM = "aes256-gcm@openssh.com"
- CipherChaCha20Poly1305 = "chacha20-poly1305@openssh.com"
- CipherAES128CTR = "aes128-ctr"
- CipherAES192CTR = "aes192-ctr"
- CipherAES256CTR = "aes256-ctr"
- InsecureCipherAES128CBC = "aes128-cbc"
- InsecureCipherTripleDESCBC = "3des-cbc"
- InsecureCipherRC4 = "arcfour"
- InsecureCipherRC4128 = "arcfour128"
- InsecureCipherRC4256 = "arcfour256"
- )
- // The key exchanges currently or previously implemented by this library, to use
- // in [Config.KeyExchanges]. For a list, see the
- // [Algorithms.KeyExchanges] returned by [SupportedAlgorithms] or
- // [InsecureAlgorithms].
- const (
- InsecureKeyExchangeDH1SHA1 = "diffie-hellman-group1-sha1"
- InsecureKeyExchangeDH14SHA1 = "diffie-hellman-group14-sha1"
- KeyExchangeDH14SHA256 = "diffie-hellman-group14-sha256"
- KeyExchangeDH16SHA512 = "diffie-hellman-group16-sha512"
- KeyExchangeECDHP256 = "ecdh-sha2-nistp256"
- KeyExchangeECDHP384 = "ecdh-sha2-nistp384"
- KeyExchangeECDHP521 = "ecdh-sha2-nistp521"
- KeyExchangeCurve25519 = "curve25519-sha256"
- InsecureKeyExchangeDHGEXSHA1 = "diffie-hellman-group-exchange-sha1"
- KeyExchangeDHGEXSHA256 = "diffie-hellman-group-exchange-sha256"
- // KeyExchangeMLKEM768X25519 is supported from Go 1.24.
- KeyExchangeMLKEM768X25519 = "mlkem768x25519-sha256"
- // An alias for KeyExchangeCurve25519SHA256. This kex ID will be added if
- // KeyExchangeCurve25519SHA256 is requested for backward compatibility with
- // OpenSSH versions up to 7.2.
- keyExchangeCurve25519LibSSH = "curve25519-sha256@libssh.org"
- )
- // The message authentication code (MAC) currently or previously implemented by
- // this library, to use in [Config.MACs]. For a list, see the
- // [Algorithms.MACs] returned by [SupportedAlgorithms] or
- // [InsecureAlgorithms].
- const (
- HMACSHA256ETM = "hmac-sha2-256-etm@openssh.com"
- HMACSHA512ETM = "hmac-sha2-512-etm@openssh.com"
- HMACSHA256 = "hmac-sha2-256"
- HMACSHA512 = "hmac-sha2-512"
- HMACSHA1 = "hmac-sha1"
- InsecureHMACSHA196 = "hmac-sha1-96"
- )
- var (
- // supportedKexAlgos specifies key-exchange algorithms implemented by this
- // package in preference order, excluding those with security issues.
- supportedKexAlgos = []string{
- KeyExchangeCurve25519,
- KeyExchangeECDHP256,
- KeyExchangeECDHP384,
- KeyExchangeECDHP521,
- KeyExchangeDH14SHA256,
- KeyExchangeDH16SHA512,
- KeyExchangeDHGEXSHA256,
- }
- // defaultKexAlgos specifies the default preference for key-exchange
- // algorithms in preference order.
- defaultKexAlgos = []string{
- KeyExchangeCurve25519,
- KeyExchangeECDHP256,
- KeyExchangeECDHP384,
- KeyExchangeECDHP521,
- KeyExchangeDH14SHA256,
- InsecureKeyExchangeDH14SHA1,
- }
- // insecureKexAlgos specifies key-exchange algorithms implemented by this
- // package and which have security issues.
- insecureKexAlgos = []string{
- InsecureKeyExchangeDH14SHA1,
- InsecureKeyExchangeDH1SHA1,
- InsecureKeyExchangeDHGEXSHA1,
- }
- // supportedCiphers specifies cipher algorithms implemented by this package
- // in preference order, excluding those with security issues.
- supportedCiphers = []string{
- CipherAES128GCM,
- CipherAES256GCM,
- CipherChaCha20Poly1305,
- CipherAES128CTR,
- CipherAES192CTR,
- CipherAES256CTR,
- }
- // defaultCiphers specifies the default preference for ciphers algorithms
- // in preference order.
- defaultCiphers = supportedCiphers
- // insecureCiphers specifies cipher algorithms implemented by this
- // package and which have security issues.
- insecureCiphers = []string{
- InsecureCipherAES128CBC,
- InsecureCipherTripleDESCBC,
- InsecureCipherRC4256,
- InsecureCipherRC4128,
- InsecureCipherRC4,
- }
- // supportedMACs specifies MAC algorithms implemented by this package in
- // preference order, excluding those with security issues.
- supportedMACs = []string{
- HMACSHA256ETM,
- HMACSHA512ETM,
- HMACSHA256,
- HMACSHA512,
- HMACSHA1,
- }
- // defaultMACs specifies the default preference for MAC algorithms in
- // preference order.
- defaultMACs = []string{
- HMACSHA256ETM,
- HMACSHA512ETM,
- HMACSHA256,
- HMACSHA512,
- HMACSHA1,
- InsecureHMACSHA196,
- }
- // insecureMACs specifies MAC algorithms implemented by this
- // package and which have security issues.
- insecureMACs = []string{
- InsecureHMACSHA196,
- }
- // supportedHostKeyAlgos specifies the supported host-key algorithms (i.e.
- // methods of authenticating servers) implemented by this package in
- // preference order, excluding those with security issues.
- supportedHostKeyAlgos = []string{
- CertAlgoRSASHA256v01,
- CertAlgoRSASHA512v01,
- CertAlgoECDSA256v01,
- CertAlgoECDSA384v01,
- CertAlgoECDSA521v01,
- CertAlgoED25519v01,
- KeyAlgoRSASHA256,
- KeyAlgoRSASHA512,
- KeyAlgoECDSA256,
- KeyAlgoECDSA384,
- KeyAlgoECDSA521,
- KeyAlgoED25519,
- }
- // defaultHostKeyAlgos specifies the default preference for host-key
- // algorithms in preference order.
- defaultHostKeyAlgos = []string{
- CertAlgoRSASHA256v01,
- CertAlgoRSASHA512v01,
- CertAlgoRSAv01,
- InsecureCertAlgoDSAv01,
- CertAlgoECDSA256v01,
- CertAlgoECDSA384v01,
- CertAlgoECDSA521v01,
- CertAlgoED25519v01,
- KeyAlgoECDSA256,
- KeyAlgoECDSA384,
- KeyAlgoECDSA521,
- KeyAlgoRSASHA256,
- KeyAlgoRSASHA512,
- KeyAlgoRSA,
- InsecureKeyAlgoDSA,
- KeyAlgoED25519,
- }
- // insecureHostKeyAlgos specifies host-key algorithms implemented by this
- // package and which have security issues.
- insecureHostKeyAlgos = []string{
- KeyAlgoRSA,
- InsecureKeyAlgoDSA,
- CertAlgoRSAv01,
- InsecureCertAlgoDSAv01,
- }
- // supportedPubKeyAuthAlgos specifies the supported client public key
- // authentication algorithms. Note that this doesn't include certificate
- // types since those use the underlying algorithm. Order is irrelevant.
- supportedPubKeyAuthAlgos = []string{
- KeyAlgoED25519,
- KeyAlgoSKED25519,
- KeyAlgoSKECDSA256,
- KeyAlgoECDSA256,
- KeyAlgoECDSA384,
- KeyAlgoECDSA521,
- KeyAlgoRSASHA256,
- KeyAlgoRSASHA512,
- }
- // defaultPubKeyAuthAlgos specifies the preferred client public key
- // authentication algorithms. This list is sent to the client if it supports
- // the server-sig-algs extension. Order is irrelevant.
- defaultPubKeyAuthAlgos = []string{
- KeyAlgoED25519,
- KeyAlgoSKED25519,
- KeyAlgoSKECDSA256,
- KeyAlgoECDSA256,
- KeyAlgoECDSA384,
- KeyAlgoECDSA521,
- KeyAlgoRSASHA256,
- KeyAlgoRSASHA512,
- KeyAlgoRSA,
- InsecureKeyAlgoDSA,
- }
- // insecurePubKeyAuthAlgos specifies client public key authentication
- // algorithms implemented by this package and which have security issues.
- insecurePubKeyAuthAlgos = []string{
- KeyAlgoRSA,
- InsecureKeyAlgoDSA,
- }
- )
- // NegotiatedAlgorithms defines algorithms negotiated between client and server.
- type NegotiatedAlgorithms struct {
- KeyExchange string
- HostKey string
- Read DirectionAlgorithms
- Write DirectionAlgorithms
- }
- // Algorithms defines a set of algorithms that can be configured in the client
- // or server config for negotiation during a handshake.
- type Algorithms struct {
- KeyExchanges []string
- Ciphers []string
- MACs []string
- HostKeys []string
- PublicKeyAuths []string
- }
- // SupportedAlgorithms returns algorithms currently implemented by this package,
- // excluding those with security issues, which are returned by
- // InsecureAlgorithms. The algorithms listed here are in preference order.
- func SupportedAlgorithms() Algorithms {
- return Algorithms{
- Ciphers: slices.Clone(supportedCiphers),
- MACs: slices.Clone(supportedMACs),
- KeyExchanges: slices.Clone(supportedKexAlgos),
- HostKeys: slices.Clone(supportedHostKeyAlgos),
- PublicKeyAuths: slices.Clone(supportedPubKeyAuthAlgos),
- }
- }
- // InsecureAlgorithms returns algorithms currently implemented by this package
- // and which have security issues.
- func InsecureAlgorithms() Algorithms {
- return Algorithms{
- KeyExchanges: slices.Clone(insecureKexAlgos),
- Ciphers: slices.Clone(insecureCiphers),
- MACs: slices.Clone(insecureMACs),
- HostKeys: slices.Clone(insecureHostKeyAlgos),
- PublicKeyAuths: slices.Clone(insecurePubKeyAuthAlgos),
- }
- }
- var supportedCompressions = []string{compressionNone}
- // hashFuncs keeps the mapping of supported signature algorithms to their
- // respective hashes needed for signing and verification.
- var hashFuncs = map[string]crypto.Hash{
- KeyAlgoRSA: crypto.SHA1,
- KeyAlgoRSASHA256: crypto.SHA256,
- KeyAlgoRSASHA512: crypto.SHA512,
- InsecureKeyAlgoDSA: crypto.SHA1,
- KeyAlgoECDSA256: crypto.SHA256,
- KeyAlgoECDSA384: crypto.SHA384,
- KeyAlgoECDSA521: crypto.SHA512,
- // KeyAlgoED25519 doesn't pre-hash.
- KeyAlgoSKECDSA256: crypto.SHA256,
- KeyAlgoSKED25519: crypto.SHA256,
- }
- // algorithmsForKeyFormat returns the supported signature algorithms for a given
- // public key format (PublicKey.Type), in order of preference. See RFC 8332,
- // Section 2. See also the note in sendKexInit on backwards compatibility.
- func algorithmsForKeyFormat(keyFormat string) []string {
- switch keyFormat {
- case KeyAlgoRSA:
- return []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA}
- case CertAlgoRSAv01:
- return []string{CertAlgoRSASHA256v01, CertAlgoRSASHA512v01, CertAlgoRSAv01}
- default:
- return []string{keyFormat}
- }
- }
- // isRSA returns whether algo is a supported RSA algorithm, including certificate
- // algorithms.
- func isRSA(algo string) bool {
- algos := algorithmsForKeyFormat(KeyAlgoRSA)
- return contains(algos, underlyingAlgo(algo))
- }
- func isRSACert(algo string) bool {
- _, ok := certKeyAlgoNames[algo]
- if !ok {
- return false
- }
- return isRSA(algo)
- }
- // unexpectedMessageError results when the SSH message that we received didn't
- // match what we wanted.
- func unexpectedMessageError(expected, got uint8) error {
- return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
- }
- // parseError results from a malformed SSH message.
- func parseError(tag uint8) error {
- return fmt.Errorf("ssh: parse error in message type %d", tag)
- }
- func findCommon(what string, client []string, server []string, isClient bool) (string, error) {
- for _, c := range client {
- for _, s := range server {
- if c == s {
- return c, nil
- }
- }
- }
- err := &AlgorithmNegotiationError{
- What: what,
- }
- if isClient {
- err.SupportedAlgorithms = client
- err.RequestedAlgorithms = server
- } else {
- err.SupportedAlgorithms = server
- err.RequestedAlgorithms = client
- }
- return "", err
- }
- // AlgorithmNegotiationError defines the error returned if the client and the
- // server cannot agree on an algorithm for key exchange, host key, cipher, MAC.
- type AlgorithmNegotiationError struct {
- What string
- // RequestedAlgorithms lists the algorithms supported by the peer.
- RequestedAlgorithms []string
- // SupportedAlgorithms lists the algorithms supported on our side.
- SupportedAlgorithms []string
- }
- func (a *AlgorithmNegotiationError) Error() string {
- return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v",
- a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
- }
- // DirectionAlgorithms defines the algorithms negotiated in one direction
- // (either read or write).
- type DirectionAlgorithms struct {
- Cipher string
- MAC string
- compression string
- }
- // rekeyBytes returns a rekeying intervals in bytes.
- func (a *DirectionAlgorithms) rekeyBytes() int64 {
- // According to RFC 4344 block ciphers should rekey after
- // 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
- // 128.
- switch a.Cipher {
- case CipherAES128CTR, CipherAES192CTR, CipherAES256CTR, CipherAES128GCM, CipherAES256GCM, InsecureCipherAES128CBC:
- return 16 * (1 << 32)
- }
- // For others, stick with RFC 4253 recommendation to rekey after 1 Gb of data.
- return 1 << 30
- }
- var aeadCiphers = map[string]bool{
- CipherAES128GCM: true,
- CipherAES256GCM: true,
- CipherChaCha20Poly1305: true,
- }
- func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) {
- result := &NegotiatedAlgorithms{}
- result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos, isClient)
- if err != nil {
- return
- }
- result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos, isClient)
- if err != nil {
- return
- }
- stoc, ctos := &result.Write, &result.Read
- if isClient {
- ctos, stoc = stoc, ctos
- }
- ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer, isClient)
- if err != nil {
- return
- }
- stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient, isClient)
- if err != nil {
- return
- }
- if !aeadCiphers[ctos.Cipher] {
- ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer, isClient)
- if err != nil {
- return
- }
- }
- if !aeadCiphers[stoc.Cipher] {
- stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient, isClient)
- if err != nil {
- return
- }
- }
- ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer, isClient)
- if err != nil {
- return
- }
- stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient, isClient)
- if err != nil {
- return
- }
- return result, nil
- }
- // If rekeythreshold is too small, we can't make any progress sending
- // stuff.
- const minRekeyThreshold uint64 = 256
- // Config contains configuration data common to both ServerConfig and
- // ClientConfig.
- type Config struct {
- // Rand provides the source of entropy for cryptographic
- // primitives. If Rand is nil, the cryptographic random reader
- // in package crypto/rand will be used.
- Rand io.Reader
- // The maximum number of bytes sent or received after which a
- // new key is negotiated. It must be at least 256. If
- // unspecified, a size suitable for the chosen cipher is used.
- RekeyThreshold uint64
- // The allowed key exchanges algorithms. If unspecified then a default set
- // of algorithms is used. Unsupported values are silently ignored.
- KeyExchanges []string
- // The allowed cipher algorithms. If unspecified then a sensible default is
- // used. Unsupported values are silently ignored.
- Ciphers []string
- // The allowed MAC algorithms. If unspecified then a sensible default is
- // used. Unsupported values are silently ignored.
- MACs []string
- }
- // SetDefaults sets sensible values for unset fields in config. This is
- // exported for testing: Configs passed to SSH functions are copied and have
- // default values set automatically.
- func (c *Config) SetDefaults() {
- if c.Rand == nil {
- c.Rand = rand.Reader
- }
- if c.Ciphers == nil {
- c.Ciphers = defaultCiphers
- }
- var ciphers []string
- for _, c := range c.Ciphers {
- if cipherModes[c] != nil {
- // Ignore the cipher if we have no cipherModes definition.
- ciphers = append(ciphers, c)
- }
- }
- c.Ciphers = ciphers
- if c.KeyExchanges == nil {
- c.KeyExchanges = defaultKexAlgos
- }
- var kexs []string
- for _, k := range c.KeyExchanges {
- if kexAlgoMap[k] != nil {
- // Ignore the KEX if we have no kexAlgoMap definition.
- kexs = append(kexs, k)
- if k == KeyExchangeCurve25519 && !contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) {
- kexs = append(kexs, keyExchangeCurve25519LibSSH)
- }
- }
- }
- c.KeyExchanges = kexs
- if c.MACs == nil {
- c.MACs = defaultMACs
- }
- var macs []string
- for _, m := range c.MACs {
- if macModes[m] != nil {
- // Ignore the MAC if we have no macModes definition.
- macs = append(macs, m)
- }
- }
- c.MACs = macs
- if c.RekeyThreshold == 0 {
- // cipher specific default
- } else if c.RekeyThreshold < minRekeyThreshold {
- c.RekeyThreshold = minRekeyThreshold
- } else if c.RekeyThreshold >= math.MaxInt64 {
- // Avoid weirdness if somebody uses -1 as a threshold.
- c.RekeyThreshold = math.MaxInt64
- }
- }
- // buildDataSignedForAuth returns the data that is signed in order to prove
- // possession of a private key. See RFC 4252, section 7. algo is the advertised
- // algorithm, and may be a certificate type.
- func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo string, pubKey []byte) []byte {
- data := struct {
- Session []byte
- Type byte
- User string
- Service string
- Method string
- Sign bool
- Algo string
- PubKey []byte
- }{
- sessionID,
- msgUserAuthRequest,
- req.User,
- req.Service,
- req.Method,
- true,
- algo,
- pubKey,
- }
- return Marshal(data)
- }
- func appendU16(buf []byte, n uint16) []byte {
- return append(buf, byte(n>>8), byte(n))
- }
- func appendU32(buf []byte, n uint32) []byte {
- return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
- }
- func appendU64(buf []byte, n uint64) []byte {
- return append(buf,
- byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
- byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
- }
- func appendInt(buf []byte, n int) []byte {
- return appendU32(buf, uint32(n))
- }
- func appendString(buf []byte, s string) []byte {
- buf = appendU32(buf, uint32(len(s)))
- buf = append(buf, s...)
- return buf
- }
- func appendBool(buf []byte, b bool) []byte {
- if b {
- return append(buf, 1)
- }
- return append(buf, 0)
- }
- // newCond is a helper to hide the fact that there is no usable zero
- // value for sync.Cond.
- func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
- // window represents the buffer available to clients
- // wishing to write to a channel.
- type window struct {
- *sync.Cond
- win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
- writeWaiters int
- closed bool
- }
- // add adds win to the amount of window available
- // for consumers.
- func (w *window) add(win uint32) bool {
- // a zero sized window adjust is a noop.
- if win == 0 {
- return true
- }
- w.L.Lock()
- if w.win+win < win {
- w.L.Unlock()
- return false
- }
- w.win += win
- // It is unusual that multiple goroutines would be attempting to reserve
- // window space, but not guaranteed. Use broadcast to notify all waiters
- // that additional window is available.
- w.Broadcast()
- w.L.Unlock()
- return true
- }
- // close sets the window to closed, so all reservations fail
- // immediately.
- func (w *window) close() {
- w.L.Lock()
- w.closed = true
- w.Broadcast()
- w.L.Unlock()
- }
- // reserve reserves win from the available window capacity.
- // If no capacity remains, reserve will block. reserve may
- // return less than requested.
- func (w *window) reserve(win uint32) (uint32, error) {
- var err error
- w.L.Lock()
- w.writeWaiters++
- w.Broadcast()
- for w.win == 0 && !w.closed {
- w.Wait()
- }
- w.writeWaiters--
- if w.win < win {
- win = w.win
- }
- w.win -= win
- if w.closed {
- err = io.EOF
- }
- w.L.Unlock()
- return win, err
- }
- // waitWriterBlocked waits until some goroutine is blocked for further
- // writes. It is used in tests only.
- func (w *window) waitWriterBlocked() {
- w.Cond.L.Lock()
- for w.writeWaiters == 0 {
- w.Cond.Wait()
- }
- w.Cond.L.Unlock()
- }
|