aboutsummaryrefslogtreecommitdiff
path: root/ssh/common.go
diff options
context:
space:
mode:
authorAdam Langley <agl@golang.org>2014-04-09 13:57:52 -0700
committerAdam Langley <agl@golang.org>2014-04-09 13:57:52 -0700
commitfa50e7408b9ef89ff2965535b59f1a0010c0770b (patch)
treee045a3f48f9ffd3bb712002f8f9f6fd489e8f7ef /ssh/common.go
parent8f45c680ceb25c200b8c301d9184532aeb7cb36e (diff)
downloadgo-x-crypto-fa50e7408b9ef89ff2965535b59f1a0010c0770b.tar.xz
go.crypto/ssh: import gosshnew.
See https://groups.google.com/d/msg/Golang-nuts/AoVxQ4bB5XQ/i8kpMxdbVlEJ R=hanwen CC=golang-codereviews https://golang.org/cl/86190043
Diffstat (limited to 'ssh/common.go')
-rw-r--r--ssh/common.go281
1 files changed, 143 insertions, 138 deletions
diff --git a/ssh/common.go b/ssh/common.go
index 4870e56..2fd7fd9 100644
--- a/ssh/common.go
+++ b/ssh/common.go
@@ -6,7 +6,9 @@ package ssh
import (
"crypto"
+ "crypto/rand"
"fmt"
+ "io"
"sync"
_ "crypto/sha1"
@@ -21,16 +23,39 @@ const (
serviceSSH = "ssh-connection"
)
+// supportedCiphers specifies the supported ciphers in preference order.
+var supportedCiphers = []string{
+ "aes128-ctr", "aes192-ctr", "aes256-ctr",
+ "aes128-gcm@openssh.com",
+ "arcfour256", "arcfour128",
+}
+
+// supportedKexAlgos specifies the supported key-exchange algorithms in
+// preference order.
var supportedKexAlgos = []string{
+ // P384 and P521 are not constant-time yet, but since we don't
+ // reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
}
+// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods
+// of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{
+ CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
+ CertAlgoECDSA384v01, CertAlgoECDSA521v01,
+
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSA, KeyAlgoDSA,
}
+// supportedMACs specifies a default set of MAC algorithms in preference order.
+// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
+// because they have reached the end of their useful life.
+var supportedMACs = []string{
+ "hmac-sha1", "hmac-sha1-96",
+}
+
var supportedCompressions = []string{compressionNone}
// hashFuncs keeps the mapping of supported algorithms to their respective
@@ -48,23 +73,15 @@ var hashFuncs = map[string]crypto.Hash{
CertAlgoECDSA521v01: crypto.SHA512,
}
-// UnexpectedMessageError results when the SSH message that we received didn't
+// unexpectedMessageError results when the SSH message that we received didn't
// match what we wanted.
-type UnexpectedMessageError struct {
- expected, got uint8
+func unexpectedMessageError(expected, got uint8) error {
+ return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected)
}
-func (u UnexpectedMessageError) Error() string {
- return fmt.Sprintf("ssh: unexpected message type %d (expected %d)", u.got, u.expected)
-}
-
-// ParseError results from a malformed SSH message.
-type ParseError struct {
- msgType uint8
-}
-
-func (p ParseError) Error() string {
- return fmt.Sprintf("ssh: parse error in message type %d", p.msgType)
+// parseError results from a malformed SSH message.
+func parseError(tag uint8) error {
+ return fmt.Errorf("ssh: parse error in message type %d", tag)
}
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
@@ -90,15 +107,17 @@ func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCip
return
}
+type directionAlgorithms struct {
+ Cipher string
+ MAC string
+ Compression string
+}
+
type algorithms struct {
- kex string
- hostKey string
- wCipher string
- rCipher string
- rMAC string
- wMAC string
- rCompression string
- wCompression string
+ kex string
+ hostKey string
+ w directionAlgorithms
+ r directionAlgorithms
}
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) {
@@ -114,32 +133,32 @@ func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algor
return
}
- result.wCipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
+ result.w.Cipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
if !ok {
return
}
- result.rCipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
+ result.r.Cipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
if !ok {
return
}
- result.wMAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
+ result.w.MAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
if !ok {
return
}
- result.rMAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
+ result.r.MAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
if !ok {
return
}
- result.wCompression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
+ result.w.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
if !ok {
return
}
- result.rCompression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
+ result.r.Compression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
if !ok {
return
}
@@ -147,133 +166,87 @@ func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algor
return result
}
-// Cryptographic configuration common to both ServerConfig and ClientConfig.
-type CryptoConfig struct {
+// 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, 1 gigabyte is used.
+ RekeyThreshold uint64
+
// The allowed key exchanges algorithms. If unspecified then a
// default set of algorithms is used.
KeyExchanges []string
- // The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
- // used.
+ // The allowed cipher algorithms. If unspecified then a sensible
+ // default is used.
Ciphers []string
- // The allowed MAC algorithms. If unspecified then DefaultMACOrder is used.
+ // The allowed MAC algorithms. If unspecified then a sensible default
+ // is used.
MACs []string
}
-func (c *CryptoConfig) ciphers() []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 {
- return DefaultCipherOrder
+ c.Ciphers = supportedCiphers
}
- return c.Ciphers
-}
-func (c *CryptoConfig) kexes() []string {
if c.KeyExchanges == nil {
- return defaultKeyExchangeOrder
+ c.KeyExchanges = supportedKexAlgos
}
- return c.KeyExchanges
-}
-func (c *CryptoConfig) macs() []string {
if c.MACs == nil {
- return DefaultMACOrder
+ c.MACs = supportedMACs
}
- return c.MACs
-}
-
-// serialize a signed slice according to RFC 4254 6.6. The name should
-// be a key type name, rather than a cert type name.
-func serializeSignature(name string, sig []byte) []byte {
- length := stringLength(len(name))
- length += stringLength(len(sig))
-
- ret := make([]byte, length)
- r := marshalString(ret, []byte(name))
- r = marshalString(r, sig)
-
- return ret
-}
-// MarshalPublicKey serializes a supported key or certificate for use
-// by the SSH wire protocol. It can be used for comparison with the
-// pubkey argument of ServerConfig's PublicKeyCallback as well as for
-// generating an authorized_keys or host_keys file.
-func MarshalPublicKey(key PublicKey) []byte {
- // See also RFC 4253 6.6.
- algoname := key.PublicKeyAlgo()
- blob := key.Marshal()
-
- length := stringLength(len(algoname))
- length += len(blob)
- ret := make([]byte, length)
- r := marshalString(ret, []byte(algoname))
- copy(r, blob)
- return ret
-}
-
-// pubAlgoToPrivAlgo returns the private key algorithm format name that
-// corresponds to a given public key algorithm format name. For most
-// public keys, the private key algorithm name is the same. For some
-// situations, such as openssh certificates, the private key algorithm and
-// public key algorithm names differ. This accounts for those situations.
-func pubAlgoToPrivAlgo(pubAlgo string) string {
- switch pubAlgo {
- case CertAlgoRSAv01:
- return KeyAlgoRSA
- case CertAlgoDSAv01:
- return KeyAlgoDSA
- case CertAlgoECDSA256v01:
- return KeyAlgoECDSA256
- case CertAlgoECDSA384v01:
- return KeyAlgoECDSA384
- case CertAlgoECDSA521v01:
- return KeyAlgoECDSA521
+ if c.RekeyThreshold == 0 {
+ // RFC 4253, section 9 suggests rekeying after 1G.
+ c.RekeyThreshold = 1 << 30
+ }
+ if c.RekeyThreshold < minRekeyThreshold {
+ c.RekeyThreshold = minRekeyThreshold
}
- return pubAlgo
}
// buildDataSignedForAuth returns the data that is signed in order to prove
// possession of a private key. See RFC 4252, section 7.
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
- user := []byte(req.User)
- service := []byte(req.Service)
- method := []byte(req.Method)
-
- length := stringLength(len(sessionId))
- length += 1
- length += stringLength(len(user))
- length += stringLength(len(service))
- length += stringLength(len(method))
- length += 1
- length += stringLength(len(algo))
- length += stringLength(len(pubKey))
-
- ret := make([]byte, length)
- r := marshalString(ret, sessionId)
- r[0] = msgUserAuthRequest
- r = r[1:]
- r = marshalString(r, user)
- r = marshalString(r, service)
- r = marshalString(r, method)
- r[0] = 1
- r = r[1:]
- r = marshalString(r, algo)
- r = marshalString(r, pubKey)
- return ret
-}
-
-// safeString sanitises s according to RFC 4251, section 9.2.
-// All control characters except tab, carriage return and newline are
-// replaced by 0x20.
-func safeString(s string) string {
- out := []byte(s)
- for i, c := range out {
- if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
- out[i] = 0x20
- }
+ data := struct {
+ Session []byte
+ Type byte
+ User string
+ Service string
+ Method string
+ Sign bool
+ Algo []byte
+ PubKey []byte
+ }{
+ sessionId,
+ msgUserAuthRequest,
+ req.User,
+ req.Service,
+ req.Method,
+ true,
+ algo,
+ pubKey,
}
- return string(out)
+ return Marshal(data)
}
func appendU16(buf []byte, n uint16) []byte {
@@ -284,6 +257,12 @@ 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))
}
@@ -296,11 +275,9 @@ func appendString(buf []byte, s string) []byte {
func appendBool(buf []byte, b bool) []byte {
if b {
- buf = append(buf, 1)
- } else {
- buf = append(buf, 0)
+ return append(buf, 1)
}
- return buf
+ return append(buf, 0)
}
// newCond is a helper to hide the fact that there is no usable zero
@@ -311,7 +288,9 @@ func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
// 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
+ 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
@@ -335,18 +314,44 @@ func (w *window) add(win uint32) bool {
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 {
+func (w *window) reserve(win uint32) (uint32, error) {
+ var err error
w.L.Lock()
- for w.win == 0 {
+ 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
+ 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()
}