diff options
| author | Adam Langley <agl@golang.org> | 2014-04-09 13:57:52 -0700 |
|---|---|---|
| committer | Adam Langley <agl@golang.org> | 2014-04-09 13:57:52 -0700 |
| commit | fa50e7408b9ef89ff2965535b59f1a0010c0770b (patch) | |
| tree | e045a3f48f9ffd3bb712002f8f9f6fd489e8f7ef /ssh/common.go | |
| parent | 8f45c680ceb25c200b8c301d9184532aeb7cb36e (diff) | |
| download | go-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.go | 281 |
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() } |
