mirror of
https://github.com/golang/go
synced 2024-11-02 13:42:29 +00:00
exp/ssh: Add support for (most) of the ciphers from RFC4253, RFC4344 and RFC4345.
R=dave, agl, taruti, rsc, r CC=golang-dev https://golang.org/cl/5342057
This commit is contained in:
parent
c638813ef6
commit
0e60804b4a
8 changed files with 206 additions and 32 deletions
|
@ -7,6 +7,7 @@ include ../../../Make.inc
|
|||
TARG=exp/ssh
|
||||
GOFILES=\
|
||||
channel.go\
|
||||
cipher.go\
|
||||
client.go\
|
||||
client_auth.go\
|
||||
common.go\
|
||||
|
|
88
src/pkg/exp/ssh/cipher.go
Normal file
88
src/pkg/exp/ssh/cipher.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// 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/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rc4"
|
||||
)
|
||||
|
||||
// streamDump is used to dump the initial keystream for stream ciphers. It is a
|
||||
// a write-only buffer, and not intended for reading so do not require a mutex.
|
||||
var streamDump [512]byte
|
||||
|
||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
||||
// by the transport before the first key-exchange.
|
||||
type noneCipher struct{}
|
||||
|
||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
||||
copy(dst, src)
|
||||
}
|
||||
|
||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewCTR(c, iv), nil
|
||||
}
|
||||
|
||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
||||
return rc4.NewCipher(key)
|
||||
}
|
||||
|
||||
type cipherMode struct {
|
||||
keySize int
|
||||
ivSize int
|
||||
skip int
|
||||
createFn func(key, iv []byte) (cipher.Stream, error)
|
||||
}
|
||||
|
||||
func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
|
||||
if len(key) < c.keySize {
|
||||
panic("ssh: key length too small for cipher")
|
||||
}
|
||||
if len(iv) < c.ivSize {
|
||||
panic("ssh: iv too small for cipher")
|
||||
}
|
||||
|
||||
stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
||||
dumpThisTime := remainingToDump
|
||||
if dumpThisTime > len(streamDump) {
|
||||
dumpThisTime = len(streamDump)
|
||||
}
|
||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
||||
remainingToDump -= dumpThisTime
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// Specifies a default set of ciphers and a preference order. This is based on
|
||||
// OpenSSH's default client preference order, minus algorithms that are not
|
||||
// implemented.
|
||||
var DefaultCipherOrder = []string{
|
||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
||||
"arcfour256", "arcfour128",
|
||||
}
|
||||
|
||||
var cipherModes = map[string]*cipherMode{
|
||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
||||
// are defined in the order specified in the RFC.
|
||||
"aes128-ctr": &cipherMode{16, aes.BlockSize, 0, newAESCTR},
|
||||
"aes192-ctr": &cipherMode{24, aes.BlockSize, 0, newAESCTR},
|
||||
"aes256-ctr": &cipherMode{32, aes.BlockSize, 0, newAESCTR},
|
||||
|
||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
||||
// They are defined in the order specified in the RFC.
|
||||
"arcfour128": &cipherMode{16, 0, 1536, newRC4},
|
||||
"arcfour256": &cipherMode{32, 0, 1536, newRC4},
|
||||
}
|
62
src/pkg/exp/ssh/cipher_test.go
Normal file
62
src/pkg/exp/ssh/cipher_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCipherReversal tests that each cipher factory produces ciphers that can
|
||||
// encrypt and decrypt some data successfully.
|
||||
func TestCipherReversal(t *testing.T) {
|
||||
testData := []byte("abcdefghijklmnopqrstuvwxyz012345")
|
||||
testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345")
|
||||
testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa")
|
||||
|
||||
cryptBuffer := make([]byte, 32)
|
||||
|
||||
for name, cipherMode := range cipherModes {
|
||||
encrypter, err := cipherMode.createCipher(testKey, testIv)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create encrypter for %q: %s", name, err)
|
||||
continue
|
||||
}
|
||||
decrypter, err := cipherMode.createCipher(testKey, testIv)
|
||||
if err != nil {
|
||||
t.Errorf("failed to create decrypter for %q: %s", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
copy(cryptBuffer, testData)
|
||||
|
||||
encrypter.XORKeyStream(cryptBuffer, cryptBuffer)
|
||||
if name == "none" {
|
||||
if !bytes.Equal(cryptBuffer, testData) {
|
||||
t.Errorf("encryption made change with 'none' cipher")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if bytes.Equal(cryptBuffer, testData) {
|
||||
t.Errorf("encryption made no change with %q", name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
decrypter.XORKeyStream(cryptBuffer, cryptBuffer)
|
||||
if !bytes.Equal(cryptBuffer, testData) {
|
||||
t.Errorf("decrypted bytes not equal to input with %q", name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultCiphersExist(t *testing.T) {
|
||||
for _, cipherAlgo := range DefaultCipherOrder {
|
||||
if _, ok := cipherModes[cipherAlgo]; !ok {
|
||||
t.Errorf("default cipher %q is unknown", cipherAlgo)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -60,8 +60,8 @@ func (c *ClientConn) handshake() error {
|
|||
clientKexInit := kexInitMsg{
|
||||
KexAlgos: supportedKexAlgos,
|
||||
ServerHostKeyAlgos: supportedHostKeyAlgos,
|
||||
CiphersClientServer: supportedCiphers,
|
||||
CiphersServerClient: supportedCiphers,
|
||||
CiphersClientServer: c.config.Crypto.ciphers(),
|
||||
CiphersServerClient: c.config.Crypto.ciphers(),
|
||||
MACsClientServer: supportedMACs,
|
||||
MACsServerClient: supportedMACs,
|
||||
CompressionClientServer: supportedCompressions,
|
||||
|
@ -301,6 +301,9 @@ type ClientConfig struct {
|
|||
// A slice of ClientAuth methods. Only the first instance
|
||||
// of a particular RFC 4252 method will be used during authentication.
|
||||
Auth []ClientAuth
|
||||
|
||||
// Cryptographic-related configuration.
|
||||
Crypto CryptoConfig
|
||||
}
|
||||
|
||||
func (c *ClientConfig) rand() io.Reader {
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
const (
|
||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||
hostAlgoRSA = "ssh-rsa"
|
||||
cipherAES128CTR = "aes128-ctr"
|
||||
macSHA196 = "hmac-sha1-96"
|
||||
compressionNone = "none"
|
||||
serviceUserAuth = "ssh-userauth"
|
||||
|
@ -25,7 +24,6 @@ const (
|
|||
|
||||
var supportedKexAlgos = []string{kexAlgoDH14SHA1}
|
||||
var supportedHostKeyAlgos = []string{hostAlgoRSA}
|
||||
var supportedCiphers = []string{cipherAES128CTR}
|
||||
var supportedMACs = []string{macSHA196}
|
||||
var supportedCompressions = []string{compressionNone}
|
||||
|
||||
|
@ -130,6 +128,20 @@ func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *ke
|
|||
return
|
||||
}
|
||||
|
||||
// Cryptographic configuration common to both ServerConfig and ClientConfig.
|
||||
type CryptoConfig struct {
|
||||
// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
|
||||
// used.
|
||||
Ciphers []string
|
||||
}
|
||||
|
||||
func (c *CryptoConfig) ciphers() []string {
|
||||
if c.Ciphers == nil {
|
||||
return DefaultCipherOrder
|
||||
}
|
||||
return c.Ciphers
|
||||
}
|
||||
|
||||
// serialize a signed slice according to RFC 4254 6.6.
|
||||
func serializeSignature(algoname string, sig []byte) []byte {
|
||||
length := stringLength([]byte(algoname))
|
||||
|
|
|
@ -448,8 +448,6 @@ func parseUint32(in []byte) (out uint32, rest []byte, ok bool) {
|
|||
return
|
||||
}
|
||||
|
||||
const maxPacketSize = 36000
|
||||
|
||||
func nameListLength(namelist []string) int {
|
||||
length := 4 /* uint32 length prefix */
|
||||
for i, name := range namelist {
|
||||
|
|
|
@ -40,6 +40,9 @@ type ServerConfig struct {
|
|||
// key authentication. It must return true iff the given public key is
|
||||
// valid for the given user.
|
||||
PubKeyCallback func(user, algo string, pubkey []byte) bool
|
||||
|
||||
// Cryptographic-related configuration.
|
||||
Crypto CryptoConfig
|
||||
}
|
||||
|
||||
func (c *ServerConfig) rand() io.Reader {
|
||||
|
@ -257,8 +260,8 @@ func (s *ServerConn) Handshake() error {
|
|||
serverKexInit := kexInitMsg{
|
||||
KexAlgos: supportedKexAlgos,
|
||||
ServerHostKeyAlgos: supportedHostKeyAlgos,
|
||||
CiphersClientServer: supportedCiphers,
|
||||
CiphersServerClient: supportedCiphers,
|
||||
CiphersClientServer: s.config.Crypto.ciphers(),
|
||||
CiphersServerClient: s.config.Crypto.ciphers(),
|
||||
MACsClientServer: supportedMACs,
|
||||
MACsServerClient: supportedMACs,
|
||||
CompressionClientServer: supportedCompressions,
|
||||
|
@ -323,7 +326,9 @@ func (s *ServerConn) Handshake() error {
|
|||
if packet[0] != msgNewKeys {
|
||||
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
||||
}
|
||||
s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc)
|
||||
if err = s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
if packet, err = s.readPacket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package ssh
|
|||
import (
|
||||
"bufio"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/subtle"
|
||||
|
@ -19,7 +18,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
paddingMultiple = 16 // TODO(dfc) does this need to be configurable?
|
||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
||||
minPacketSize = 16
|
||||
maxPacketSize = 36000
|
||||
minPaddingSize = 4 // TODO(huin) should this be configurable?
|
||||
)
|
||||
|
||||
// filteredConn reduces the set of methods exposed when embeddeding
|
||||
|
@ -61,8 +63,7 @@ type reader struct {
|
|||
type writer struct {
|
||||
*sync.Mutex // protects writer.Writer from concurrent writes
|
||||
*bufio.Writer
|
||||
paddingMultiple int
|
||||
rand io.Reader
|
||||
rand io.Reader
|
||||
common
|
||||
}
|
||||
|
||||
|
@ -82,14 +83,11 @@ type common struct {
|
|||
func (r *reader) readOnePacket() ([]byte, error) {
|
||||
var lengthBytes = make([]byte, 5)
|
||||
var macSize uint32
|
||||
|
||||
if _, err := io.ReadFull(r, lengthBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.cipher != nil {
|
||||
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
||||
}
|
||||
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
||||
|
||||
if r.mac != nil {
|
||||
r.mac.Reset()
|
||||
|
@ -153,9 +151,9 @@ func (w *writer) writePacket(packet []byte) error {
|
|||
w.Mutex.Lock()
|
||||
defer w.Mutex.Unlock()
|
||||
|
||||
paddingLength := paddingMultiple - (5+len(packet))%paddingMultiple
|
||||
paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
|
||||
if paddingLength < 4 {
|
||||
paddingLength += paddingMultiple
|
||||
paddingLength += packetSizeMultiple
|
||||
}
|
||||
|
||||
length := len(packet) + 1 + paddingLength
|
||||
|
@ -188,11 +186,9 @@ func (w *writer) writePacket(packet []byte) error {
|
|||
|
||||
// TODO(dfc) lengthBytes, packet and padding should be
|
||||
// subslices of a single buffer
|
||||
if w.cipher != nil {
|
||||
w.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
||||
w.cipher.XORKeyStream(packet, packet)
|
||||
w.cipher.XORKeyStream(padding, padding)
|
||||
}
|
||||
w.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
||||
w.cipher.XORKeyStream(packet, packet)
|
||||
w.cipher.XORKeyStream(padding, padding)
|
||||
|
||||
if _, err := w.Write(lengthBytes); err != nil {
|
||||
return err
|
||||
|
@ -227,11 +223,17 @@ func newTransport(conn net.Conn, rand io.Reader) *transport {
|
|||
return &transport{
|
||||
reader: reader{
|
||||
Reader: bufio.NewReader(conn),
|
||||
common: common{
|
||||
cipher: noneCipher{},
|
||||
},
|
||||
},
|
||||
writer: writer{
|
||||
Writer: bufio.NewWriter(conn),
|
||||
rand: rand,
|
||||
Mutex: new(sync.Mutex),
|
||||
common: common{
|
||||
cipher: noneCipher{},
|
||||
},
|
||||
},
|
||||
filteredConn: conn,
|
||||
}
|
||||
|
@ -249,29 +251,32 @@ var (
|
|||
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
||||
)
|
||||
|
||||
// setupKeys sets the cipher and MAC keys from K, H and sessionId, as
|
||||
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
||||
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
||||
func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
|
||||
h := hashFunc.New()
|
||||
cipherMode := cipherModes[c.cipherAlgo]
|
||||
|
||||
blockSize := 16
|
||||
keySize := 16
|
||||
macKeySize := 20
|
||||
|
||||
iv := make([]byte, blockSize)
|
||||
key := make([]byte, keySize)
|
||||
iv := make([]byte, cipherMode.ivSize)
|
||||
key := make([]byte, cipherMode.keySize)
|
||||
macKey := make([]byte, macKeySize)
|
||||
|
||||
h := hashFunc.New()
|
||||
generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
|
||||
generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
|
||||
generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
|
||||
|
||||
c.mac = truncatingMAC{12, hmac.NewSHA1(macKey)}
|
||||
aes, err := aes.NewCipher(key)
|
||||
|
||||
cipher, err := cipherMode.createCipher(key, iv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.cipher = cipher.NewCTR(aes, iv)
|
||||
|
||||
c.cipher = cipher
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue