Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fixes golang/go#17676

Change-Id: I96c51431b174898a6bc0f6bec7f4561d5d64819f
  • Loading branch information
MiLk committed Jan 21, 2017
1 parent b8a2a83 commit 87b8127
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 10 deletions.
73 changes: 67 additions & 6 deletions ssh/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ const prefixLen = 5
type streamPacketCipher struct {
mac hash.Hash
cipher cipher.Stream
etm bool

// The following members are to avoid per-packet allocations.
prefix [prefixLen]byte
Expand All @@ -150,7 +151,15 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
return nil, err
}

s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
var encryptedPaddingLength []byte
if s.mac != nil && s.etm {
encryptedPaddingLength = make([]byte, 1)
copy(encryptedPaddingLength[:], s.prefix[4:5])
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
} else {
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
}

length := binary.BigEndian.Uint32(s.prefix[0:4])
paddingLength := uint32(s.prefix[4])

Expand All @@ -159,7 +168,13 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
s.mac.Write(s.prefix[:])
if s.etm {
s.mac.Write(s.prefix[:4])
s.mac.Write(encryptedPaddingLength)

} else {
s.mac.Write(s.prefix[:])
}
macSize = uint32(s.mac.Size())
}

Expand All @@ -184,10 +199,17 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
}
mac := s.packetData[length-1:]
data := s.packetData[:length-1]

if s.mac != nil && s.etm {
s.mac.Write(data)
}

s.cipher.XORKeyStream(data, data)

if s.mac != nil {
s.mac.Write(data)
if !s.etm {
s.mac.Write(data)
}
s.macResult = s.mac.Sum(s.macResult[:0])
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
return nil, errors.New("ssh: MAC failure")
Expand All @@ -203,7 +225,13 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
return errors.New("ssh: packet too large")
}

paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
aadlen := 0
if s.mac != nil && s.etm {
// packet length is not encrypted for EtM modes
aadlen = 4
}

paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple
if paddingLength < 4 {
paddingLength += packetSizeMultiple
}
Expand All @@ -216,7 +244,14 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
return err
}

if s.mac != nil {
if s.mac != nil && !s.etm {
// After key exchange, the 'mac' for the selected MAC
// algorithm will be computed before encryption from the concatenation
// of packet data:
// mac = MAC(key, sequence_number || unencrypted_packet)
// where unencrypted_packet is the entire packet without 'mac' (the
// length fields, 'payload' and 'random padding'), and sequence_number
// is an implicit packet sequence number represented as uint32.
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
Expand All @@ -225,10 +260,36 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
s.mac.Write(padding)
}

s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
if s.mac != nil && s.etm {
// Specifically, the "-etm" MAC algorithms modify the transport protocol
// to calculate the MAC over the packet ciphertext and to send the packet
// length unencrypted. This is necessary for the transport to obtain the
// length of the packet and location of the MAC tag so that it may be
// verified without decrypting unauthenticated data.
// As such, the MAC covers:
// mac = MAC(key, sequence_number || packet_length || encrypted_packet)
// where "packet_length" is encoded as a uint32.
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
s.mac.Write(s.prefix[:])
} else {
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
}

s.cipher.XORKeyStream(packet, packet)
s.cipher.XORKeyStream(padding, padding)

if s.mac != nil && s.etm {
// "encrypted_packet" contains:
// byte padding_length
// byte[n1] payload; n1 = packet_length - padding_length - 1
// byte[n2] random padding; n2 = padding_length
s.mac.Write(packet)
s.mac.Write(padding)
}

if _, err := w.Write(s.prefix[:]); err != nil {
return err
}
Expand Down
39 changes: 39 additions & 0 deletions ssh/cipher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,45 @@ func TestPacketCiphers(t *testing.T) {
}
}

func TestPacketMacs(t *testing.T) {
for mac := range macModes {
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: "aes256-ctr",
MAC: mac,
Compression: "none",
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Errorf("newPacketCipher(client, %q): %v", mac, err)
continue
}
server, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
t.Errorf("newPacketCipher(client, %q): %v", mac, err)
continue
}

want := "bla bla"
input := []byte(want)
buf := &bytes.Buffer{}
if err := client.writePacket(0, buf, rand.Reader, input); err != nil {
t.Errorf("writePacket(%q): %v", mac, err)
continue
}

packet, err := server.readPacket(0, buf)
if err != nil {
t.Errorf("readPacket(%q): %v", mac, err)
continue
}

if string(packet) != want {
t.Errorf("roundtrip(%q): got %q, want %q", mac, packet, want)
}
}
}

func TestCBCOracleCounterMeasure(t *testing.T) {
cipherModes[aes128cbcID] = &streamCipherMode{16, aes.BlockSize, 0, nil}
defer delete(cipherModes, aes128cbcID)
Expand Down
2 changes: 1 addition & 1 deletion ssh/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var supportedHostKeyAlgos = []string{
// 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-sha2-256", "hmac-sha1", "hmac-sha1-96",
"hmac-sha2-256[email protected]", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
}

var supportedCompressions = []string{compressionNone}
Expand Down
10 changes: 7 additions & 3 deletions ssh/mac.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

type macMode struct {
keySize int
etm bool
new func(key []byte) hash.Hash
}

Expand Down Expand Up @@ -45,13 +46,16 @@ func (t truncatingMAC) Size() int {
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }

var macModes = map[string]*macMode{
"hmac-sha2-256": {32, func(key []byte) hash.Hash {
"hmac-sha2-256[email protected]": {32, true, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, func(key []byte) hash.Hash {
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
return hmac.New(sha1.New, key)
}},
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
return truncatingMAC{12, hmac.New(sha1.New, key)}
}},
}
23 changes: 23 additions & 0 deletions ssh/test/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,29 @@ func TestMACs(t *testing.T) {
}
}

func TestMACsEtM(t *testing.T) {
var config ssh.Config
config.SetDefaults()
macOrder := []string{}
for _, mac := range config.MACs {
if len(mac) > 16 && mac[len(mac)-16:] == "[email protected]" {
macOrder = append(macOrder, mac)
}
}

for _, mac := range macOrder {
server := newServer(t)
defer server.Shutdown()
conf := clientConfig()
conf.MACs = []string{mac}
if conn, err := server.TryDial(conf); err == nil {
conn.Close()
} else {
t.Fatalf("failed for MAC %q", mac)
}
}
}

func TestKeyExchanges(t *testing.T) {
var config ssh.Config
config.SetDefaults()
Expand Down
1 change: 1 addition & 0 deletions ssh/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac

c := &streamPacketCipher{
mac: macModes[algs.MAC].new(macKey),
etm: macModes[algs.MAC].etm,
}
c.macResult = make([]byte, c.mac.Size())

Expand Down

0 comments on commit 87b8127

Please sign in to comment.