365 строки
9.9 KiB
Go
365 строки
9.9 KiB
Go
// 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/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/rsa"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
)
|
|
|
|
// These are string constants in the SSH protocol.
|
|
const (
|
|
keyAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
|
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
|
hostAlgoRSA = "ssh-rsa"
|
|
hostAlgoDSA = "ssh-dss"
|
|
compressionNone = "none"
|
|
serviceUserAuth = "ssh-userauth"
|
|
serviceSSH = "ssh-connection"
|
|
)
|
|
|
|
var supportedKexAlgos = []string{kexAlgoDH14SHA1, keyAlgoDH1SHA1}
|
|
var supportedHostKeyAlgos = []string{hostAlgoRSA}
|
|
var supportedCompressions = []string{compressionNone}
|
|
|
|
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
|
type dhGroup struct {
|
|
g, p *big.Int
|
|
}
|
|
|
|
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
|
if theirPublic.Sign() <= 0 || theirPublic.Cmp(group.p) >= 0 {
|
|
return nil, errors.New("ssh: DH parameter out of bounds")
|
|
}
|
|
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
|
}
|
|
|
|
// dhGroup1 is the group called diffie-hellman-group1-sha1 in RFC 4253 and
|
|
// Oakley Group 2 in RFC 2409.
|
|
var dhGroup1 *dhGroup
|
|
|
|
var dhGroup1Once sync.Once
|
|
|
|
func initDHGroup1() {
|
|
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
|
|
|
dhGroup1 = &dhGroup{
|
|
g: new(big.Int).SetInt64(2),
|
|
p: p,
|
|
}
|
|
}
|
|
|
|
// dhGroup14 is the group called diffie-hellman-group14-sha1 in RFC 4253 and
|
|
// Oakley Group 14 in RFC 3526.
|
|
var dhGroup14 *dhGroup
|
|
|
|
var dhGroup14Once sync.Once
|
|
|
|
func initDHGroup14() {
|
|
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
|
|
|
dhGroup14 = &dhGroup{
|
|
g: new(big.Int).SetInt64(2),
|
|
p: p,
|
|
}
|
|
}
|
|
|
|
// UnexpectedMessageError results when the SSH message that we received didn't
|
|
// match what we wanted.
|
|
type UnexpectedMessageError struct {
|
|
expected, got uint8
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
type handshakeMagics struct {
|
|
clientVersion, serverVersion []byte
|
|
clientKexInit, serverKexInit []byte
|
|
}
|
|
|
|
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
|
|
for _, clientAlgo := range clientAlgos {
|
|
for _, serverAlgo := range serverAlgos {
|
|
if clientAlgo == serverAlgo {
|
|
return clientAlgo, true
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) {
|
|
for _, clientCipher := range clientCiphers {
|
|
for _, serverCipher := range serverCiphers {
|
|
// reject the cipher if we have no cipherModes definition
|
|
if clientCipher == serverCipher && cipherModes[clientCipher] != nil {
|
|
return clientCipher, true
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *kexInitMsg) (kexAlgo, hostKeyAlgo string, ok bool) {
|
|
kexAlgo, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
hostKeyAlgo, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.writer.cipherAlgo, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.reader.cipherAlgo, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.writer.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.reader.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.writer.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
transport.reader.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
ok = true
|
|
return
|
|
}
|
|
|
|
// Cryptographic configuration common to both ServerConfig and ClientConfig.
|
|
type CryptoConfig struct {
|
|
// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
|
|
// used.
|
|
Ciphers []string
|
|
|
|
// The allowed MAC algorithms. If unspecified then DefaultMACOrder is used.
|
|
MACs []string
|
|
}
|
|
|
|
func (c *CryptoConfig) ciphers() []string {
|
|
if c.Ciphers == nil {
|
|
return DefaultCipherOrder
|
|
}
|
|
return c.Ciphers
|
|
}
|
|
|
|
func (c *CryptoConfig) macs() []string {
|
|
if c.MACs == nil {
|
|
return DefaultMACOrder
|
|
}
|
|
return c.MACs
|
|
}
|
|
|
|
// serialize a signed slice according to RFC 4254 6.6.
|
|
func serializeSignature(algoname string, sig []byte) []byte {
|
|
switch algoname {
|
|
// The corresponding private key to a public certificate is always a normal
|
|
// private key. For signature serialization purposes, ensure we use the
|
|
// proper key algorithm name in case the public cert algorithm name is passed.
|
|
case CertAlgoRSAv01:
|
|
algoname = KeyAlgoRSA
|
|
case CertAlgoDSAv01:
|
|
algoname = KeyAlgoDSA
|
|
case CertAlgoECDSA256v01:
|
|
algoname = KeyAlgoECDSA256
|
|
case CertAlgoECDSA384v01:
|
|
algoname = KeyAlgoECDSA384
|
|
case CertAlgoECDSA521v01:
|
|
algoname = KeyAlgoECDSA521
|
|
}
|
|
length := stringLength(len(algoname))
|
|
length += stringLength(len(sig))
|
|
|
|
ret := make([]byte, length)
|
|
r := marshalString(ret, []byte(algoname))
|
|
r = marshalString(r, sig)
|
|
|
|
return ret
|
|
}
|
|
|
|
// serialize a *rsa.PublicKey or *dsa.PublicKey according to RFC 4253 6.6.
|
|
func serializePublickey(key interface{}) []byte {
|
|
var pubKeyBytes []byte
|
|
algoname := algoName(key)
|
|
switch key := key.(type) {
|
|
case *rsa.PublicKey:
|
|
pubKeyBytes = marshalPubRSA(key)
|
|
case *dsa.PublicKey:
|
|
pubKeyBytes = marshalPubDSA(key)
|
|
case *ecdsa.PublicKey:
|
|
pubKeyBytes = marshalPubECDSA(key)
|
|
case *OpenSSHCertV01:
|
|
pubKeyBytes = marshalOpenSSHCertV01(key)
|
|
default:
|
|
panic("unexpected key type")
|
|
}
|
|
|
|
length := stringLength(len(algoname))
|
|
length += len(pubKeyBytes)
|
|
ret := make([]byte, length)
|
|
r := marshalString(ret, []byte(algoname))
|
|
copy(r, pubKeyBytes)
|
|
return ret
|
|
}
|
|
|
|
func algoName(key interface{}) string {
|
|
switch key.(type) {
|
|
case *rsa.PublicKey:
|
|
return KeyAlgoRSA
|
|
case *dsa.PublicKey:
|
|
return KeyAlgoDSA
|
|
case *ecdsa.PublicKey:
|
|
switch key.(*ecdsa.PublicKey).Params().BitSize {
|
|
case 256:
|
|
return KeyAlgoECDSA256
|
|
case 384:
|
|
return KeyAlgoECDSA384
|
|
case 521:
|
|
return KeyAlgoECDSA521
|
|
}
|
|
case *OpenSSHCertV01:
|
|
return algoName(key.(*OpenSSHCertV01).Key) + "-cert-v01@openssh.com"
|
|
}
|
|
panic("unexpected key type")
|
|
}
|
|
|
|
// buildDataSignedForAuth returns the data that is signed in order to prove
|
|
// posession 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
|
|
}
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
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 appendInt(buf []byte, n int) []byte {
|
|
return appendU32(buf, uint32(n))
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 {
|
|
w.L.Lock()
|
|
for w.win == 0 {
|
|
w.Wait()
|
|
}
|
|
if w.win < win {
|
|
win = w.win
|
|
}
|
|
w.win -= win
|
|
w.L.Unlock()
|
|
return win
|
|
}
|