Commit 0e60804b authored by John Beisley's avatar John Beisley Committed by Adam Langley

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
parent c638813e
...@@ -7,6 +7,7 @@ include ../../../Make.inc ...@@ -7,6 +7,7 @@ include ../../../Make.inc
TARG=exp/ssh TARG=exp/ssh
GOFILES=\ GOFILES=\
channel.go\ channel.go\
cipher.go\
client.go\ client.go\
client_auth.go\ client_auth.go\
common.go\ common.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/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},
}
// 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 { ...@@ -60,8 +60,8 @@ func (c *ClientConn) handshake() error {
clientKexInit := kexInitMsg{ clientKexInit := kexInitMsg{
KexAlgos: supportedKexAlgos, KexAlgos: supportedKexAlgos,
ServerHostKeyAlgos: supportedHostKeyAlgos, ServerHostKeyAlgos: supportedHostKeyAlgos,
CiphersClientServer: supportedCiphers, CiphersClientServer: c.config.Crypto.ciphers(),
CiphersServerClient: supportedCiphers, CiphersServerClient: c.config.Crypto.ciphers(),
MACsClientServer: supportedMACs, MACsClientServer: supportedMACs,
MACsServerClient: supportedMACs, MACsServerClient: supportedMACs,
CompressionClientServer: supportedCompressions, CompressionClientServer: supportedCompressions,
...@@ -301,6 +301,9 @@ type ClientConfig struct { ...@@ -301,6 +301,9 @@ type ClientConfig struct {
// A slice of ClientAuth methods. Only the first instance // A slice of ClientAuth methods. Only the first instance
// of a particular RFC 4252 method will be used during authentication. // of a particular RFC 4252 method will be used during authentication.
Auth []ClientAuth Auth []ClientAuth
// Cryptographic-related configuration.
Crypto CryptoConfig
} }
func (c *ClientConfig) rand() io.Reader { func (c *ClientConfig) rand() io.Reader {
......
...@@ -16,7 +16,6 @@ import ( ...@@ -16,7 +16,6 @@ import (
const ( const (
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1" kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
hostAlgoRSA = "ssh-rsa" hostAlgoRSA = "ssh-rsa"
cipherAES128CTR = "aes128-ctr"
macSHA196 = "hmac-sha1-96" macSHA196 = "hmac-sha1-96"
compressionNone = "none" compressionNone = "none"
serviceUserAuth = "ssh-userauth" serviceUserAuth = "ssh-userauth"
...@@ -25,7 +24,6 @@ const ( ...@@ -25,7 +24,6 @@ const (
var supportedKexAlgos = []string{kexAlgoDH14SHA1} var supportedKexAlgos = []string{kexAlgoDH14SHA1}
var supportedHostKeyAlgos = []string{hostAlgoRSA} var supportedHostKeyAlgos = []string{hostAlgoRSA}
var supportedCiphers = []string{cipherAES128CTR}
var supportedMACs = []string{macSHA196} var supportedMACs = []string{macSHA196}
var supportedCompressions = []string{compressionNone} var supportedCompressions = []string{compressionNone}
...@@ -130,6 +128,20 @@ func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *ke ...@@ -130,6 +128,20 @@ func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *ke
return 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. // serialize a signed slice according to RFC 4254 6.6.
func serializeSignature(algoname string, sig []byte) []byte { func serializeSignature(algoname string, sig []byte) []byte {
length := stringLength([]byte(algoname)) length := stringLength([]byte(algoname))
......
...@@ -448,8 +448,6 @@ func parseUint32(in []byte) (out uint32, rest []byte, ok bool) { ...@@ -448,8 +448,6 @@ func parseUint32(in []byte) (out uint32, rest []byte, ok bool) {
return return
} }
const maxPacketSize = 36000
func nameListLength(namelist []string) int { func nameListLength(namelist []string) int {
length := 4 /* uint32 length prefix */ length := 4 /* uint32 length prefix */
for i, name := range namelist { for i, name := range namelist {
......
...@@ -40,6 +40,9 @@ type ServerConfig struct { ...@@ -40,6 +40,9 @@ type ServerConfig struct {
// key authentication. It must return true iff the given public key is // key authentication. It must return true iff the given public key is
// valid for the given user. // valid for the given user.
PubKeyCallback func(user, algo string, pubkey []byte) bool PubKeyCallback func(user, algo string, pubkey []byte) bool
// Cryptographic-related configuration.
Crypto CryptoConfig
} }
func (c *ServerConfig) rand() io.Reader { func (c *ServerConfig) rand() io.Reader {
...@@ -257,8 +260,8 @@ func (s *ServerConn) Handshake() error { ...@@ -257,8 +260,8 @@ func (s *ServerConn) Handshake() error {
serverKexInit := kexInitMsg{ serverKexInit := kexInitMsg{
KexAlgos: supportedKexAlgos, KexAlgos: supportedKexAlgos,
ServerHostKeyAlgos: supportedHostKeyAlgos, ServerHostKeyAlgos: supportedHostKeyAlgos,
CiphersClientServer: supportedCiphers, CiphersClientServer: s.config.Crypto.ciphers(),
CiphersServerClient: supportedCiphers, CiphersServerClient: s.config.Crypto.ciphers(),
MACsClientServer: supportedMACs, MACsClientServer: supportedMACs,
MACsServerClient: supportedMACs, MACsServerClient: supportedMACs,
CompressionClientServer: supportedCompressions, CompressionClientServer: supportedCompressions,
...@@ -323,7 +326,9 @@ func (s *ServerConn) Handshake() error { ...@@ -323,7 +326,9 @@ func (s *ServerConn) Handshake() error {
if packet[0] != msgNewKeys { if packet[0] != msgNewKeys {
return UnexpectedMessageError{msgNewKeys, packet[0]} 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 { if packet, err = s.readPacket(); err != nil {
return err return err
} }
......
...@@ -7,7 +7,6 @@ package ssh ...@@ -7,7 +7,6 @@ package ssh
import ( import (
"bufio" "bufio"
"crypto" "crypto"
"crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/hmac" "crypto/hmac"
"crypto/subtle" "crypto/subtle"
...@@ -19,7 +18,10 @@ import ( ...@@ -19,7 +18,10 @@ import (
) )
const ( 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 // filteredConn reduces the set of methods exposed when embeddeding
...@@ -61,8 +63,7 @@ type reader struct { ...@@ -61,8 +63,7 @@ type reader struct {
type writer struct { type writer struct {
*sync.Mutex // protects writer.Writer from concurrent writes *sync.Mutex // protects writer.Writer from concurrent writes
*bufio.Writer *bufio.Writer
paddingMultiple int rand io.Reader
rand io.Reader
common common
} }
...@@ -82,14 +83,11 @@ type common struct { ...@@ -82,14 +83,11 @@ type common struct {
func (r *reader) readOnePacket() ([]byte, error) { func (r *reader) readOnePacket() ([]byte, error) {
var lengthBytes = make([]byte, 5) var lengthBytes = make([]byte, 5)
var macSize uint32 var macSize uint32
if _, err := io.ReadFull(r, lengthBytes); err != nil { if _, err := io.ReadFull(r, lengthBytes); err != nil {
return nil, err return nil, err
} }
if r.cipher != nil { r.cipher.XORKeyStream(lengthBytes, lengthBytes)
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
}
if r.mac != nil { if r.mac != nil {
r.mac.Reset() r.mac.Reset()
...@@ -153,9 +151,9 @@ func (w *writer) writePacket(packet []byte) error { ...@@ -153,9 +151,9 @@ func (w *writer) writePacket(packet []byte) error {
w.Mutex.Lock() w.Mutex.Lock()
defer w.Mutex.Unlock() defer w.Mutex.Unlock()
paddingLength := paddingMultiple - (5+len(packet))%paddingMultiple paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
if paddingLength < 4 { if paddingLength < 4 {
paddingLength += paddingMultiple paddingLength += packetSizeMultiple
} }
length := len(packet) + 1 + paddingLength length := len(packet) + 1 + paddingLength
...@@ -188,11 +186,9 @@ func (w *writer) writePacket(packet []byte) error { ...@@ -188,11 +186,9 @@ func (w *writer) writePacket(packet []byte) error {
// TODO(dfc) lengthBytes, packet and padding should be // TODO(dfc) lengthBytes, packet and padding should be
// subslices of a single buffer // subslices of a single buffer
if w.cipher != nil { w.cipher.XORKeyStream(lengthBytes, lengthBytes)
w.cipher.XORKeyStream(lengthBytes, lengthBytes) w.cipher.XORKeyStream(packet, packet)
w.cipher.XORKeyStream(packet, packet) w.cipher.XORKeyStream(padding, padding)
w.cipher.XORKeyStream(padding, padding)
}
if _, err := w.Write(lengthBytes); err != nil { if _, err := w.Write(lengthBytes); err != nil {
return err return err
...@@ -227,11 +223,17 @@ func newTransport(conn net.Conn, rand io.Reader) *transport { ...@@ -227,11 +223,17 @@ func newTransport(conn net.Conn, rand io.Reader) *transport {
return &transport{ return &transport{
reader: reader{ reader: reader{
Reader: bufio.NewReader(conn), Reader: bufio.NewReader(conn),
common: common{
cipher: noneCipher{},
},
}, },
writer: writer{ writer: writer{
Writer: bufio.NewWriter(conn), Writer: bufio.NewWriter(conn),
rand: rand, rand: rand,
Mutex: new(sync.Mutex), Mutex: new(sync.Mutex),
common: common{
cipher: noneCipher{},
},
}, },
filteredConn: conn, filteredConn: conn,
} }
...@@ -249,29 +251,32 @@ var ( ...@@ -249,29 +251,32 @@ var (
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} 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 // described in RFC 4253, section 6.4. direction should either be serverKeys
// (to setup server->client keys) or clientKeys (for client->server keys). // (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 { 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 macKeySize := 20
iv := make([]byte, blockSize) iv := make([]byte, cipherMode.ivSize)
key := make([]byte, keySize) key := make([]byte, cipherMode.keySize)
macKey := make([]byte, macKeySize) macKey := make([]byte, macKeySize)
h := hashFunc.New()
generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h) generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
generateKeyMaterial(key, d.keyTag, K, H, sessionId, h) generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h) generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
c.mac = truncatingMAC{12, hmac.NewSHA1(macKey)} c.mac = truncatingMAC{12, hmac.NewSHA1(macKey)}
aes, err := aes.NewCipher(key)
cipher, err := cipherMode.createCipher(key, iv)
if err != nil { if err != nil {
return err return err
} }
c.cipher = cipher.NewCTR(aes, iv)
c.cipher = cipher
return nil return nil
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment