Navigation Menu

Skip to content

Commit

Permalink
crypto/tls: support SSLv3
Browse files Browse the repository at this point in the history
It would be nice not to have to support this since all the clients
that we care about support TLSv1 by now. However, due to buggy
implementations of SSLv3 on the Internet which can't do version
negotiation correctly, browsers will sometimes switch to SSLv3. Since
there's no good way for a browser tell a network problem from a buggy
server, this downgrade can occur even if the server in question is
actually working correctly.

So we need to support SSLv3 for robustness :(

Fixes golang#1703.

R=bradfitz
CC=golang-dev
https://golang.org/cl/5018045
  • Loading branch information
agl committed Sep 14, 2011
1 parent 3908284 commit 76c2ff5
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 117 deletions.
84 changes: 76 additions & 8 deletions cipher_suites.go
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/cipher"
"crypto/hmac"
"crypto/rc4"
"crypto/sha1"
"crypto/x509"
"hash"
"os"
Expand All @@ -23,7 +24,7 @@ type keyAgreement interface {
// ServerKeyExchange message, generateServerKeyExchange can return nil,
// nil.
generateServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, os.Error)
processClientKeyExchange(*Config, *clientKeyExchangeMsg) ([]byte, os.Error)
processClientKeyExchange(*Config, *clientKeyExchangeMsg, uint16) ([]byte, os.Error)

// On the client side, the next two methods are called in order.

Expand All @@ -46,14 +47,14 @@ type cipherSuite struct {
// and point format that we can handle.
elliptic bool
cipher func(key, iv []byte, isRead bool) interface{}
mac func(macKey []byte) hash.Hash
mac func(version uint16, macKey []byte) macFunction
}

var cipherSuites = map[uint16]*cipherSuite{
TLS_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, rsaKA, false, cipherRC4, hmacSHA1},
TLS_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, rsaKA, false, cipherAES, hmacSHA1},
TLS_ECDHE_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, ecdheRSAKA, true, cipherRC4, hmacSHA1},
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, ecdheRSAKA, true, cipherAES, hmacSHA1},
TLS_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, rsaKA, false, cipherRC4, macSHA1},
TLS_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, rsaKA, false, cipherAES, macSHA1},
TLS_ECDHE_RSA_WITH_RC4_128_SHA: &cipherSuite{16, 20, 0, ecdheRSAKA, true, cipherRC4, macSHA1},
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: &cipherSuite{16, 20, 16, ecdheRSAKA, true, cipherAES, macSHA1},
}

func cipherRC4(key, iv []byte, isRead bool) interface{} {
Expand All @@ -69,8 +70,75 @@ func cipherAES(key, iv []byte, isRead bool) interface{} {
return cipher.NewCBCEncrypter(block, iv)
}

func hmacSHA1(key []byte) hash.Hash {
return hmac.NewSHA1(key)
// macSHA1 returns a macFunction for the given protocol version.
func macSHA1(version uint16, key []byte) macFunction {
if version == versionSSL30 {
mac := ssl30MAC{
h: sha1.New(),
key: make([]byte, len(key)),
}
copy(mac.key, key)
return mac
}
return tls10MAC{hmac.NewSHA1(key)}
}

type macFunction interface {
Size() int
MAC(seq, data []byte) []byte
}

// ssl30MAC implements the SSLv3 MAC function, as defined in
// www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 5.2.3.1
type ssl30MAC struct {
h hash.Hash
key []byte
}

func (s ssl30MAC) Size() int {
return s.h.Size()
}

var ssl30Pad1 = [48]byte{0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36}

var ssl30Pad2 = [48]byte{0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c}

func (s ssl30MAC) MAC(seq, record []byte) []byte {
padLength := 48
if s.h.Size() == 20 {
padLength = 40
}

s.h.Reset()
s.h.Write(s.key)
s.h.Write(ssl30Pad1[:padLength])
s.h.Write(seq)
s.h.Write(record[:1])
s.h.Write(record[3:5])
s.h.Write(record[recordHeaderLen:])
digest := s.h.Sum()

s.h.Reset()
s.h.Write(s.key)
s.h.Write(ssl30Pad2[:padLength])
s.h.Write(digest)
return s.h.Sum()
}

// tls10MAC implements the TLS 1.0 MAC function. RFC 2246, section 6.2.3.
type tls10MAC struct {
h hash.Hash
}

func (s tls10MAC) Size() int {
return s.h.Size()
}

func (s tls10MAC) MAC(seq, record []byte) []byte {
s.h.Reset()
s.h.Write(seq)
s.h.Write(record)
return s.h.Sum()
}

func rsaKA() keyAgreement {
Expand Down
7 changes: 5 additions & 2 deletions common.go
Expand Up @@ -20,8 +20,11 @@ const (
recordHeaderLen = 5 // record header length
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)

minVersion = 0x0301 // minimum supported version - TLS 1.0
maxVersion = 0x0301 // maximum supported version - TLS 1.0
versionSSL30 = 0x0300
versionTLS10 = 0x0301

minVersion = versionSSL30
maxVersion = versionTLS10
)

// TLS record types.
Expand Down
50 changes: 33 additions & 17 deletions conn.go
Expand Up @@ -11,7 +11,6 @@ import (
"crypto/cipher"
"crypto/subtle"
"crypto/x509"
"hash"
"io"
"net"
"os"
Expand Down Expand Up @@ -108,18 +107,20 @@ func (c *Conn) SetWriteTimeout(nsec int64) os.Error {
// connection, either sending or receiving.
type halfConn struct {
sync.Mutex
cipher interface{} // cipher algorithm
mac hash.Hash // MAC algorithm
seq [8]byte // 64-bit sequence number
bfree *block // list of free blocks
version uint16 // protocol version
cipher interface{} // cipher algorithm
mac macFunction
seq [8]byte // 64-bit sequence number
bfree *block // list of free blocks

nextCipher interface{} // next encryption state
nextMac hash.Hash // next MAC algorithm
nextMac macFunction // next MAC algorithm
}

// prepareCipherSpec sets the encryption and MAC states
// that a subsequent changeCipherSpec will use.
func (hc *halfConn) prepareCipherSpec(cipher interface{}, mac hash.Hash) {
func (hc *halfConn) prepareCipherSpec(version uint16, cipher interface{}, mac macFunction) {
hc.version = version
hc.nextCipher = cipher
hc.nextMac = mac
}
Expand Down Expand Up @@ -197,6 +198,22 @@ func removePadding(payload []byte) ([]byte, byte) {
return payload[:len(payload)-int(toRemove)], good
}

// removePaddingSSL30 is a replacement for removePadding in the case that the
// protocol version is SSLv3. In this version, the contents of the padding
// are random and cannot be checked.
func removePaddingSSL30(payload []byte) ([]byte, byte) {
if len(payload) < 1 {
return payload, 0
}

paddingLen := int(payload[len(payload)-1]) + 1
if paddingLen > len(payload) {
return payload, 0
}

return payload[:len(payload)-paddingLen], 255
}

func roundUp(a, b int) int {
return a + (b-a%b)%b
}
Expand Down Expand Up @@ -226,7 +243,11 @@ func (hc *halfConn) decrypt(b *block) (bool, alert) {
}

c.CryptBlocks(payload, payload)
payload, paddingGood = removePadding(payload)
if hc.version == versionSSL30 {
payload, paddingGood = removePaddingSSL30(payload)
} else {
payload, paddingGood = removePadding(payload)
}
b.resize(recordHeaderLen + len(payload))

// note that we still have a timing side-channel in the
Expand Down Expand Up @@ -256,13 +277,10 @@ func (hc *halfConn) decrypt(b *block) (bool, alert) {
b.data[4] = byte(n)
b.resize(recordHeaderLen + n)
remoteMAC := payload[n:]

hc.mac.Reset()
hc.mac.Write(hc.seq[0:])
localMAC := hc.mac.MAC(hc.seq[0:], b.data)
hc.incSeq()
hc.mac.Write(b.data)

if subtle.ConstantTimeCompare(hc.mac.Sum(), remoteMAC) != 1 || paddingGood != 255 {
if subtle.ConstantTimeCompare(localMAC, remoteMAC) != 1 || paddingGood != 255 {
return false, alertBadRecordMAC
}
}
Expand Down Expand Up @@ -291,11 +309,9 @@ func padToBlockSize(payload []byte, blockSize int) (prefix, finalBlock []byte) {
func (hc *halfConn) encrypt(b *block) (bool, alert) {
// mac
if hc.mac != nil {
hc.mac.Reset()
hc.mac.Write(hc.seq[0:])
mac := hc.mac.MAC(hc.seq[0:], b.data)
hc.incSeq()
hc.mac.Write(b.data)
mac := hc.mac.Sum()

n := len(b.data)
b.resize(n + len(mac))
copy(b.data[n:], mac)
Expand Down
12 changes: 6 additions & 6 deletions handshake_client.go
Expand Up @@ -14,7 +14,7 @@ import (
)

func (c *Conn) clientHandshake() os.Error {
finishedHash := newFinishedHash()
finishedHash := newFinishedHash(versionTLS10)

if c.config == nil {
c.config = defaultConfig()
Expand Down Expand Up @@ -247,11 +247,11 @@ func (c *Conn) clientHandshake() os.Error {
}

masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromPreMasterSecret10(preMasterSecret, hello.random, serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)
keysFromPreMasterSecret(c.vers, preMasterSecret, hello.random, serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)

clientCipher := suite.cipher(clientKey, clientIV, false /* not for reading */ )
clientHash := suite.mac(clientMAC)
c.out.prepareCipherSpec(clientCipher, clientHash)
clientHash := suite.mac(c.vers, clientMAC)
c.out.prepareCipherSpec(c.vers, clientCipher, clientHash)
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

if serverHello.nextProtoNeg {
Expand All @@ -271,8 +271,8 @@ func (c *Conn) clientHandshake() os.Error {
c.writeRecord(recordTypeHandshake, finished.marshal())

serverCipher := suite.cipher(serverKey, serverIV, true /* for reading */ )
serverHash := suite.mac(serverMAC)
c.in.prepareCipherSpec(serverCipher, serverHash)
serverHash := suite.mac(c.vers, serverMAC)
c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)
c.readRecord(recordTypeChangeCipherSpec)
if c.err != nil {
return c.err
Expand Down
1 change: 1 addition & 0 deletions handshake_client_test.go
Expand Up @@ -18,6 +18,7 @@ func testClientScript(t *testing.T, name string, clientScript [][]byte, config *
go func() {
cli.Write([]byte("hello\n"))
cli.Close()
c.Close()
}()

defer c.Close()
Expand Down
6 changes: 3 additions & 3 deletions handshake_messages.go
Expand Up @@ -676,17 +676,17 @@ func (m *finishedMsg) marshal() (x []byte) {
return m.raw
}

x = make([]byte, 16)
x = make([]byte, 4+len(m.verifyData))
x[0] = typeFinished
x[3] = 12
x[3] = byte(len(m.verifyData))
copy(x[4:], m.verifyData)
m.raw = x
return
}

func (m *finishedMsg) unmarshal(data []byte) bool {
m.raw = data
if len(data) != 4+12 {
if len(data) < 4 {
return false
}
m.verifyData = data[4:]
Expand Down
13 changes: 7 additions & 6 deletions handshake_messages_test.go
Expand Up @@ -14,13 +14,13 @@ import (
var tests = []interface{}{
&clientHelloMsg{},
&serverHelloMsg{},
&finishedMsg{},

&certificateMsg{},
&certificateRequestMsg{},
&certificateVerifyMsg{},
&certificateStatusMsg{},
&clientKeyExchangeMsg{},
&finishedMsg{},
&nextProtoMsg{},
}

Expand Down Expand Up @@ -59,11 +59,12 @@ func TestMarshalUnmarshal(t *testing.T) {
break
}

if i >= 2 {
// The first two message types (ClientHello and
// ServerHello) are allowed to have parsable
// prefixes because the extension data is
// optional.
if i >= 3 {
// The first three message types (ClientHello,
// ServerHello and Finished) are allowed to
// have parsable prefixes because the extension
// data is optional and the length of the
// Finished varies across versions.
for j := 0; j < len(marshaled); j++ {
if m2.unmarshal(marshaled[0:j]) {
t.Errorf("#%d unmarshaled a prefix of length %d of %#v", i, j, m1)
Expand Down
15 changes: 7 additions & 8 deletions handshake_server.go
Expand Up @@ -30,7 +30,7 @@ func (c *Conn) serverHandshake() os.Error {
c.vers = vers
c.haveVers = true

finishedHash := newFinishedHash()
finishedHash := newFinishedHash(vers)
finishedHash.Write(clientHello.marshal())

hello := new(serverHelloMsg)
Expand Down Expand Up @@ -128,7 +128,6 @@ FindCipherSuite:
}

keyAgreement := suite.ka()

skx, err := keyAgreement.generateServerKeyExchange(config, clientHello, hello)
if err != nil {
c.sendAlert(alertHandshakeFailure)
Expand Down Expand Up @@ -235,18 +234,18 @@ FindCipherSuite:
finishedHash.Write(certVerify.marshal())
}

preMasterSecret, err := keyAgreement.processClientKeyExchange(config, ckx)
preMasterSecret, err := keyAgreement.processClientKeyExchange(config, ckx, c.vers)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
}

masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromPreMasterSecret10(preMasterSecret, clientHello.random, hello.random, suite.macLen, suite.keyLen, suite.ivLen)
keysFromPreMasterSecret(c.vers, preMasterSecret, clientHello.random, hello.random, suite.macLen, suite.keyLen, suite.ivLen)

clientCipher := suite.cipher(clientKey, clientIV, true /* for reading */ )
clientHash := suite.mac(clientMAC)
c.in.prepareCipherSpec(clientCipher, clientHash)
clientHash := suite.mac(c.vers, clientMAC)
c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
c.readRecord(recordTypeChangeCipherSpec)
if err := c.error(); err != nil {
return err
Expand Down Expand Up @@ -283,8 +282,8 @@ FindCipherSuite:
finishedHash.Write(clientFinished.marshal())

serverCipher := suite.cipher(serverKey, serverIV, false /* not for reading */ )
serverHash := suite.mac(serverMAC)
c.out.prepareCipherSpec(serverCipher, serverHash)
serverHash := suite.mac(c.vers, serverMAC)
c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})

finished := new(finishedMsg)
Expand Down

0 comments on commit 76c2ff5

Please sign in to comment.