-
-
Save FiloSottile/8e0fe1a3523126e8c581 to your computer and use it in GitHub Desktop.
From 6ec6e3f7b176547783b2c464d54bc1a1f7d884f7 Mon Sep 17 00:00:00 2001 | |
From: Filippo Valsorda <filippo@cloudflare.com> | |
Date: Mon, 7 Dec 2015 15:44:34 +0000 | |
Subject: [PATCH] crypto/tls: support SSLv2 compatibility handshakes | |
--- | |
src/crypto/tls/conn.go | 103 ++++++++++++++++++++++++++++++++++++- | |
src/crypto/tls/handshake_server.go | 7 ++- | |
2 files changed, 107 insertions(+), 3 deletions(-) | |
diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go | |
index e3dcf15..b723ae7 100644 | |
--- a/src/crypto/tls/conn.go | |
+++ b/src/crypto/tls/conn.go | |
@@ -46,6 +46,9 @@ type Conn struct { | |
// firstFinished contains the first Finished hash sent during the | |
// handshake. This is the "tls-unique" channel binding value. | |
firstFinished [12]byte | |
+ // v2ClientHello contains the CH as sent by the client if it's been | |
+ // unwrapped from a SSLv2 compatibility handshake | |
+ v2ClientHello []byte | |
clientProtocol string | |
clientProtocolFallback bool | |
@@ -507,6 +510,103 @@ func (hc *halfConn) splitBlock(b *block, n int) (*block, *block) { | |
return b, bb | |
} | |
+func unwrapSSLv2Handshake(c *Conn, b *block) error { | |
+ // The high bit must be 1 for a SSLv2 compatible ClientHello | |
+ if b.data[0]&128 != 128 { | |
+ c.sendAlert(alertUnexpectedMessage) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ recordLength := uint16(b.data[0]&0x7f)<<8 | uint16(b.data[1]) | |
+ mType := uint8(b.data[2]) | |
+ majVer := uint8(b.data[3]) | |
+ minVer := uint8(b.data[4]) | |
+ | |
+ if mType != typeClientHello { | |
+ c.sendAlert(alertUnexpectedMessage) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ // We can only unwrap SSLv2 ClientHello if TLS/SSLv3 is supported | |
+ if majVer < 3 { | |
+ c.sendAlert(alertProtocolVersion) | |
+ return c.in.setErrorLocked(errors.New("tls: unsupported version in v2 handshake")) | |
+ } | |
+ | |
+ if err := b.readFromUntil(c.conn, int(2+recordLength)); err != nil { | |
+ if err == io.EOF { | |
+ err = io.ErrUnexpectedEOF | |
+ } | |
+ if e, ok := err.(net.Error); !ok || !e.Temporary() { | |
+ c.in.setErrorLocked(err) | |
+ } | |
+ return err | |
+ } | |
+ | |
+ cipherSpecLen := uint16(b.data[5])<<8 | uint16(b.data[6]) | |
+ if (cipherSpecLen % 3) != 0 { | |
+ c.sendAlert(alertHandshakeFailure) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ // Session ID length MUST be zero for a SSLv2 ClientHello | |
+ sessionIdLen := uint16(b.data[7])<<8 | uint16(b.data[8]) | |
+ if sessionIdLen != 0 { | |
+ c.sendAlert(alertHandshakeFailure) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ challengeLen := uint16(b.data[9])<<8 | uint16(b.data[10]) | |
+ // The spec is contradictory here -- it says challengeLen must be 32, but | |
+ // also specifies how to handle challenge lengths greater or less than that | |
+ // Anyway according to rfc2246 must have at least 16 bytes of challenge data | |
+ // See also "Netscape SSLv2 challenge length bug" in | |
+ // http://www.dcs.ed.ac.uk/home/crypto/SSLeay/vendor-bugs.html | |
+ if challengeLen < 16 { | |
+ c.sendAlert(alertHandshakeFailure) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ if recordLength != 9+cipherSpecLen+challengeLen { | |
+ c.sendAlert(alertHandshakeFailure) | |
+ return c.in.setErrorLocked(errors.New("tls: v2 handshake unwrapping error")) | |
+ } | |
+ | |
+ cipherSpecs := b.data[11 : 11+cipherSpecLen] | |
+ challengeData := b.data[11+cipherSpecLen : 11+cipherSpecLen+challengeLen] | |
+ | |
+ b, c.rawInput = c.in.splitBlock(b, int(2+recordLength)) | |
+ | |
+ // Create a normal ClientHello message and write it to the handshake buffer | |
+ helloMsg := clientHelloMsg{} | |
+ helloMsg.vers = uint16(majVer)<<8 | uint16(minVer) | |
+ helloMsg.sessionId = []byte{0} // Session ID must be zero | |
+ helloMsg.compressionMethods = []uint8{compressionNone} | |
+ // Only use the last 32 bytes of the challenge data, padded to the right | |
+ if len(challengeData) < 32 { | |
+ helloMsg.random = make([]byte, 32-len(challengeData)) | |
+ helloMsg.random = append(helloMsg.random, challengeData...) | |
+ } else { | |
+ helloMsg.random = challengeData[len(challengeData)-32:] | |
+ } | |
+ | |
+ for i := 0; i < len(cipherSpecs); i += 3 { | |
+ if cipherSpecs[i] != 0 { | |
+ continue | |
+ } | |
+ cipher := uint16(cipherSpecs[i+1])<<8 | uint16(cipherSpecs[i+2]) | |
+ helloMsg.cipherSuites = append(helloMsg.cipherSuites, cipher) | |
+ } | |
+ | |
+ c.hand.Write(helloMsg.marshal()) | |
+ | |
+ c.v2ClientHello = make([]byte, recordLength) | |
+ copy(c.v2ClientHello, b.data[2:]) | |
+ c.in.freeBlock(b) | |
+ | |
+ return nil | |
+} | |
+ | |
// readRecord reads the next TLS record from the connection | |
// and updates the record layer state. | |
// c.in.Mutex <= L; c.input == nil. | |
@@ -556,8 +656,7 @@ Again: | |
// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests | |
// an SSLv2 client. | |
if want == recordTypeHandshake && typ == 0x80 { | |
- c.sendAlert(alertProtocolVersion) | |
- return c.in.setErrorLocked(errors.New("tls: unsupported SSLv2 handshake received")) | |
+ return unwrapSSLv2Handshake(c, b) | |
} | |
vers := uint16(b.data[1])<<8 | uint16(b.data[2]) | |
diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go | |
index e16cddc..9df9b68 100644 | |
--- a/src/crypto/tls/handshake_server.go | |
+++ b/src/crypto/tls/handshake_server.go | |
@@ -352,7 +352,12 @@ func (hs *serverHandshakeState) doFullHandshake() error { | |
// certificates won't be used. | |
hs.finishedHash.discardHandshakeBuffer() | |
} | |
- hs.finishedHash.Write(hs.clientHello.marshal()) | |
+ if c.v2ClientHello != nil { | |
+ hs.finishedHash.Write(c.v2ClientHello) | |
+ c.v2ClientHello = nil | |
+ } else { | |
+ hs.finishedHash.Write(hs.clientHello.marshal()) | |
+ } | |
hs.finishedHash.Write(hs.hello.marshal()) | |
c.writeRecord(recordTypeHandshake, hs.hello.marshal()) | |
-- | |
2.7.0 | |
The setting of helloMsg.random has a bug; challengeData[32-len(challengeData):] only works when len() is 32, but the code is trying to work with it being longer too. Also, many of the errors.New() are the same constant string giving no clue as to the error site, and it would be nice to have the errant value in the error too, e.g. if sessionIdLen isn't zero, what is it.
challengeLen
is a uint16 but line 79 of this diff has if len(challengeLen) < 16 {
should that just be if challengeLen < 16
?
As that seems to be a compile error @FiloSottile i'm curious how much mileage you've gotten out of this patch.
@jehiah ah, that's a merge mistake, thanks. The patch I was using actually had that check commented out. You will also notice it fails to return an error correctly (c.sendAlert
returns nil on success).
I'll fix this and look into the other two comments, but again, THIS IS NOT SAFE TO RELY ON.
@RalphCorderoy thanks, that was clearly meant to be len(challengeData)-32
, and it would have caused a crash, fixed in the new version.
Now I want to see if go-fuzz would have found it.
BTW, I think in practice most non-OpenSSL/SSLeay clients uses a 16 byte challenge in the SSLv2 ClientHello. See "Netscape SSLv2 challenge length bug" in http://www.dcs.ed.ac.uk/home/crypto/SSLeay/vendor-bugs.html for why. They fixed this text in the TLS 1.2 spec.