crypto/tls (part 3)
(With hindsight, record_process might have been designed wrong, but it works for now. It'll get redrawn when client support is added.) R=rsc CC=r http://go/go-review/1018032
This commit is contained in:
parent
7cfc31151a
commit
ebe78b393d
232
handshake_server.go
Normal file
232
handshake_server.go
Normal file
@ -0,0 +1,232 @@
|
||||
// Copyright 2009 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 tls
|
||||
|
||||
// The handshake goroutine reads handshake messages from the record processor
|
||||
// and outputs messages to be written on another channel. It updates the record
|
||||
// processor with the state of the connection via the control channel. In the
|
||||
// case of handshake messages that need synchronous processing (because they
|
||||
// affect the handling of the next record) the record processor knows about
|
||||
// them and either waits for a control message (Finished) or includes a reply
|
||||
// channel in the message (ChangeCipherSpec).
|
||||
|
||||
import (
|
||||
"crypto/hmac";
|
||||
"crypto/rc4";
|
||||
"crypto/rsa";
|
||||
"crypto/sha1";
|
||||
"crypto/subtle";
|
||||
"io";
|
||||
)
|
||||
|
||||
type cipherSuite struct {
|
||||
id uint16; // The number of this suite on the wire.
|
||||
hashLength, cipherKeyLength int;
|
||||
// TODO(agl): need a method to create the cipher and hash interfaces.
|
||||
}
|
||||
|
||||
var cipherSuites = []cipherSuite{
|
||||
cipherSuite{TLS_RSA_WITH_RC4_128_SHA, 20, 16},
|
||||
}
|
||||
|
||||
// A serverHandshake performs the server side of the TLS 1.1 handshake protocol.
|
||||
type serverHandshake struct {
|
||||
writeChan chan<- interface{};
|
||||
controlChan chan<- interface{};
|
||||
msgChan <-chan interface{};
|
||||
config *Config;
|
||||
}
|
||||
|
||||
func (h *serverHandshake) loop(writeChan chan<- interface{}, controlChan chan<- interface{}, msgChan <-chan interface{}, config *Config) {
|
||||
h.writeChan = writeChan;
|
||||
h.controlChan = controlChan;
|
||||
h.msgChan = msgChan;
|
||||
h.config = config;
|
||||
|
||||
defer close(writeChan);
|
||||
defer close(controlChan);
|
||||
|
||||
clientHello, ok := h.readHandshakeMsg().(*clientHelloMsg);
|
||||
if !ok {
|
||||
h.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
major, minor, ok := mutualVersion(clientHello.major, clientHello.minor);
|
||||
if !ok {
|
||||
h.error(alertProtocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
finishedHash := newFinishedHash();
|
||||
finishedHash.Write(clientHello.marshal());
|
||||
|
||||
hello := new(serverHelloMsg);
|
||||
|
||||
// We only support a single ciphersuite so we look for it in the list
|
||||
// of client supported suites.
|
||||
//
|
||||
// TODO(agl): Add additional cipher suites.
|
||||
var suite *cipherSuite;
|
||||
|
||||
for _, id := range clientHello.cipherSuites {
|
||||
for _, supported := range cipherSuites {
|
||||
if supported.id == id {
|
||||
suite = &supported;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundCompression := false;
|
||||
// We only support null compression, so check that the client offered it.
|
||||
for _, compression := range clientHello.compressionMethods {
|
||||
if compression == compressionNone {
|
||||
foundCompression = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if suite == nil || !foundCompression {
|
||||
h.error(alertHandshakeFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
hello.major = major;
|
||||
hello.minor = minor;
|
||||
hello.cipherSuite = suite.id;
|
||||
currentTime := uint32(config.Time());
|
||||
hello.random = make([]byte, 32);
|
||||
hello.random[0] = byte(currentTime >> 24);
|
||||
hello.random[1] = byte(currentTime >> 16);
|
||||
hello.random[2] = byte(currentTime >> 8);
|
||||
hello.random[3] = byte(currentTime);
|
||||
_, err := io.ReadFull(config.Rand, hello.random[4:len(hello.random)]);
|
||||
if err != nil {
|
||||
h.error(alertInternalError);
|
||||
return;
|
||||
}
|
||||
hello.compressionMethod = compressionNone;
|
||||
|
||||
finishedHash.Write(hello.marshal());
|
||||
writeChan <- writerSetVersion{major, minor};
|
||||
writeChan <- hello;
|
||||
|
||||
if len(config.Certificates) == 0 {
|
||||
h.error(alertInternalError);
|
||||
return;
|
||||
}
|
||||
|
||||
certMsg := new(certificateMsg);
|
||||
certMsg.certificates = config.Certificates[0].Certificate;
|
||||
finishedHash.Write(certMsg.marshal());
|
||||
writeChan <- certMsg;
|
||||
|
||||
helloDone := new(serverHelloDoneMsg);
|
||||
finishedHash.Write(helloDone.marshal());
|
||||
writeChan <- helloDone;
|
||||
|
||||
ckx, ok := h.readHandshakeMsg().(*clientKeyExchangeMsg);
|
||||
if !ok {
|
||||
h.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
finishedHash.Write(ckx.marshal());
|
||||
|
||||
preMasterSecret := make([]byte, 48);
|
||||
_, err = io.ReadFull(config.Rand, preMasterSecret[2:len(preMasterSecret)]);
|
||||
if err != nil {
|
||||
h.error(alertInternalError);
|
||||
return;
|
||||
}
|
||||
|
||||
err = rsa.DecryptPKCS1v15SessionKey(config.Rand, config.Certificates[0].PrivateKey, ckx.ciphertext, preMasterSecret);
|
||||
if err != nil {
|
||||
h.error(alertHandshakeFailure);
|
||||
return;
|
||||
}
|
||||
// We don't check the version number in the premaster secret. For one,
|
||||
// by checking it, we would leak information about the validity of the
|
||||
// encrypted pre-master secret. Secondly, it provides only a small
|
||||
// benefit against a downgrade attack and some implementations send the
|
||||
// wrong version anyway. See the discussion at the end of section
|
||||
// 7.4.7.1 of RFC 4346.
|
||||
|
||||
masterSecret, clientMAC, serverMAC, clientKey, serverKey :=
|
||||
keysFromPreMasterSecret11(preMasterSecret, clientHello.random, hello.random, suite.hashLength, suite.cipherKeyLength);
|
||||
|
||||
_, ok = h.readHandshakeMsg().(changeCipherSpec);
|
||||
if !ok {
|
||||
h.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
cipher, _ := rc4.NewCipher(clientKey);
|
||||
controlChan <- &newCipherSpec{cipher, hmac.New(sha1.New(), clientMAC)};
|
||||
|
||||
clientFinished, ok := h.readHandshakeMsg().(*finishedMsg);
|
||||
if !ok {
|
||||
h.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
verify := finishedHash.clientSum(masterSecret);
|
||||
if len(verify) != len(clientFinished.verifyData) ||
|
||||
subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
|
||||
h.error(alertHandshakeFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
controlChan <- ConnectionState{true, "TLS_RSA_WITH_RC4_128_SHA", 0};
|
||||
|
||||
finishedHash.Write(clientFinished.marshal());
|
||||
|
||||
cipher2, _ := rc4.NewCipher(serverKey);
|
||||
writeChan <- writerChangeCipherSpec{cipher2, hmac.New(sha1.New(), serverMAC)};
|
||||
|
||||
finished := new(finishedMsg);
|
||||
finished.verifyData = finishedHash.serverSum(masterSecret);
|
||||
writeChan <- finished;
|
||||
|
||||
writeChan <- writerEnableApplicationData{};
|
||||
|
||||
for {
|
||||
_, ok := h.readHandshakeMsg().(*clientHelloMsg);
|
||||
if !ok {
|
||||
h.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
// We reject all renegotication requests.
|
||||
writeChan <- alert{alertLevelWarning, alertNoRenegotiation};
|
||||
}
|
||||
}
|
||||
|
||||
func (h *serverHandshake) readHandshakeMsg() interface{} {
|
||||
v := <-h.msgChan;
|
||||
if closed(h.msgChan) {
|
||||
// If the channel closed then the processor received an error
|
||||
// from the peer and we don't want to echo it back to them.
|
||||
h.msgChan = nil;
|
||||
return 0;
|
||||
}
|
||||
if _, ok := v.(alert); ok {
|
||||
// We got an alert from the processor. We forward to the writer
|
||||
// and shutdown.
|
||||
h.writeChan <- v;
|
||||
h.msgChan = nil;
|
||||
return 0;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
func (h *serverHandshake) error(e alertType) {
|
||||
if h.msgChan != nil {
|
||||
// If we didn't get an error from the processor, then we need
|
||||
// to tell it about the error.
|
||||
h.controlChan <- ConnectionState{false, "", e};
|
||||
close(h.controlChan);
|
||||
go func() { for _ = range h.msgChan {} }();
|
||||
h.writeChan <- alert{alertLevelError, e};
|
||||
}
|
||||
}
|
210
handshake_server_test.go
Normal file
210
handshake_server_test.go
Normal file
@ -0,0 +1,210 @@
|
||||
// Copyright 2009 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 tls
|
||||
|
||||
import (
|
||||
"bytes";
|
||||
"big";
|
||||
"crypto/rsa";
|
||||
"os";
|
||||
"testing";
|
||||
"testing/script";
|
||||
)
|
||||
|
||||
type zeroSource struct{}
|
||||
|
||||
func (zeroSource) Read(b []byte) (n int, err os.Error) {
|
||||
for i := range b {
|
||||
b[i] = 0;
|
||||
}
|
||||
|
||||
return len(b), nil;
|
||||
}
|
||||
|
||||
var testConfig *Config
|
||||
|
||||
func init() {
|
||||
testConfig = new(Config);
|
||||
testConfig.Time = func() int64 { return 0 };
|
||||
testConfig.Rand = zeroSource{};
|
||||
testConfig.Certificates = make([]Certificate, 1);
|
||||
testConfig.Certificates[0].Certificate = [][]byte{testCertificate};
|
||||
testConfig.Certificates[0].PrivateKey = testPrivateKey;
|
||||
}
|
||||
|
||||
func setupServerHandshake() (writeChan chan interface{}, controlChan chan interface{}, msgChan chan interface{}) {
|
||||
sh := new(serverHandshake);
|
||||
writeChan = make(chan interface{});
|
||||
controlChan = make(chan interface{});
|
||||
msgChan = make(chan interface{});
|
||||
|
||||
go sh.loop(writeChan, controlChan, msgChan, testConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
func testClientHelloFailure(t *testing.T, clientHello interface{}, expectedAlert alertType) {
|
||||
writeChan, controlChan, msgChan := setupServerHandshake();
|
||||
defer close(msgChan);
|
||||
|
||||
send := script.NewEvent("send", nil, script.Send{msgChan, clientHello});
|
||||
recvAlert := script.NewEvent("recv alert", []*script.Event{send}, script.Recv{writeChan, alert{alertLevelError, expectedAlert}});
|
||||
close1 := script.NewEvent("msgChan close", []*script.Event{recvAlert}, script.Closed{writeChan});
|
||||
recvState := script.NewEvent("recv state", []*script.Event{send}, script.Recv{controlChan, ConnectionState{false, "", expectedAlert}});
|
||||
close2 := script.NewEvent("controlChan close", []*script.Event{recvState}, script.Closed{controlChan});
|
||||
|
||||
err := script.Perform(0, []*script.Event{send, recvAlert, close1, recvState, close2});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleError(t *testing.T) {
|
||||
testClientHelloFailure(t, &serverHelloDoneMsg{}, alertUnexpectedMessage);
|
||||
}
|
||||
|
||||
var badProtocolVersions = []uint8{0, 0, 0, 5, 1, 0, 1, 5, 2, 0, 2, 5, 3, 0}
|
||||
|
||||
func TestRejectBadProtocolVersion(t *testing.T) {
|
||||
clientHello := new(clientHelloMsg);
|
||||
|
||||
for i := 0; i < len(badProtocolVersions); i += 2 {
|
||||
clientHello.major = badProtocolVersions[i];
|
||||
clientHello.minor = badProtocolVersions[i+1];
|
||||
|
||||
testClientHelloFailure(t, clientHello, alertProtocolVersion);
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSuiteOverlap(t *testing.T) {
|
||||
clientHello := &clientHelloMsg{nil, 3, 1, nil, nil, []uint16{0xff00}, []uint8{0}};
|
||||
testClientHelloFailure(t, clientHello, alertHandshakeFailure);
|
||||
|
||||
}
|
||||
|
||||
func TestNoCompressionOverlap(t *testing.T) {
|
||||
clientHello := &clientHelloMsg{nil, 3, 1, nil, nil, []uint16{TLS_RSA_WITH_RC4_128_SHA}, []uint8{0xff}};
|
||||
testClientHelloFailure(t, clientHello, alertHandshakeFailure);
|
||||
}
|
||||
|
||||
func matchServerHello(v interface{}) bool {
|
||||
serverHello, ok := v.(*serverHelloMsg);
|
||||
if !ok {
|
||||
return false;
|
||||
}
|
||||
return serverHello.major == 3 &&
|
||||
serverHello.minor == 2 &&
|
||||
serverHello.cipherSuite == TLS_RSA_WITH_RC4_128_SHA &&
|
||||
serverHello.compressionMethod == compressionNone;
|
||||
}
|
||||
|
||||
func TestAlertForwarding(t *testing.T) {
|
||||
writeChan, controlChan, msgChan := setupServerHandshake();
|
||||
defer close(msgChan);
|
||||
|
||||
a := alert{alertLevelError, alertNoRenegotiation};
|
||||
sendAlert := script.NewEvent("send alert", nil, script.Send{msgChan, a});
|
||||
recvAlert := script.NewEvent("recv alert", []*script.Event{sendAlert}, script.Recv{writeChan, a});
|
||||
closeWriter := script.NewEvent("close writer", []*script.Event{recvAlert}, script.Closed{writeChan});
|
||||
closeControl := script.NewEvent("close control", []*script.Event{recvAlert}, script.Closed{controlChan});
|
||||
|
||||
err := script.Perform(0, []*script.Event{sendAlert, recvAlert, closeWriter, closeControl});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
writeChan, controlChan, msgChan := setupServerHandshake();
|
||||
|
||||
close := script.NewEvent("close", nil, script.Close{msgChan});
|
||||
closed1 := script.NewEvent("closed1", []*script.Event{close}, script.Closed{writeChan});
|
||||
closed2 := script.NewEvent("closed2", []*script.Event{close}, script.Closed{controlChan});
|
||||
|
||||
err := script.Perform(0, []*script.Event{close, closed1, closed2});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func matchCertificate(v interface{}) bool {
|
||||
cert, ok := v.(*certificateMsg);
|
||||
if !ok {
|
||||
return false;
|
||||
}
|
||||
return len(cert.certificates) == 1 &&
|
||||
bytes.Compare(cert.certificates[0], testCertificate) == 0;
|
||||
}
|
||||
|
||||
func matchSetCipher(v interface{}) bool {
|
||||
_, ok := v.(writerChangeCipherSpec);
|
||||
return ok;
|
||||
}
|
||||
|
||||
func matchDone(v interface{}) bool {
|
||||
_, ok := v.(*serverHelloDoneMsg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
func matchFinished(v interface{}) bool {
|
||||
finished, ok := v.(*finishedMsg);
|
||||
if !ok {
|
||||
return false;
|
||||
}
|
||||
return bytes.Compare(finished.verifyData, fromHex("29122ae11453e631487b02ed")) == 0;
|
||||
}
|
||||
|
||||
func matchNewCipherSpec(v interface{}) bool {
|
||||
_, ok := v.(*newCipherSpec);
|
||||
return ok;
|
||||
}
|
||||
|
||||
func TestFullHandshake(t *testing.T) {
|
||||
writeChan, controlChan, msgChan := setupServerHandshake();
|
||||
defer close(msgChan);
|
||||
|
||||
// The values for this test were obtained from running `gnutls-cli --insecure --debug 9`
|
||||
clientHello := &clientHelloMsg{fromHex("0100007603024aef7d77e4686d5dfd9d953dfe280788759ffd440867d687670216da45516b310000340033004500390088001600320044003800870013006600900091008f008e002f004100350084000a00050004008c008d008b008a01000019000900030200010000000e000c0000093132372e302e302e31"), 3, 2, fromHex("4aef7d77e4686d5dfd9d953dfe280788759ffd440867d687670216da45516b31"), nil, []uint16{0x33, 0x45, 0x39, 0x88, 0x16, 0x32, 0x44, 0x38, 0x87, 0x13, 0x66, 0x90, 0x91, 0x8f, 0x8e, 0x2f, 0x41, 0x35, 0x84, 0xa, 0x5, 0x4, 0x8c, 0x8d, 0x8b, 0x8a}, []uint8{0x0}};
|
||||
|
||||
sendHello := script.NewEvent("send hello", nil, script.Send{msgChan, clientHello});
|
||||
setVersion := script.NewEvent("set version", []*script.Event{sendHello}, script.Recv{writeChan, writerSetVersion{3, 2}});
|
||||
recvHello := script.NewEvent("recv hello", []*script.Event{setVersion}, script.RecvMatch{writeChan, matchServerHello});
|
||||
recvCert := script.NewEvent("recv cert", []*script.Event{recvHello}, script.RecvMatch{writeChan, matchCertificate});
|
||||
recvDone := script.NewEvent("recv done", []*script.Event{recvCert}, script.RecvMatch{writeChan, matchDone});
|
||||
|
||||
ckx := &clientKeyExchangeMsg{nil, fromHex("872e1fee5f37dd86f3215938ac8de20b302b90074e9fb93097e6b7d1286d0f45abf2daf179deb618bb3c70ed0afee6ee24476ee4649e5a23358143c0f1d9c251")};
|
||||
sendCKX := script.NewEvent("send ckx", []*script.Event{recvDone}, script.Send{msgChan, ckx});
|
||||
|
||||
sendCCS := script.NewEvent("send ccs", []*script.Event{sendCKX}, script.Send{msgChan, changeCipherSpec{}});
|
||||
recvNCS := script.NewEvent("recv done", []*script.Event{sendCCS}, script.RecvMatch{controlChan, matchNewCipherSpec});
|
||||
|
||||
finished := &finishedMsg{nil, fromHex("c8faca5d242f4423325c5b1a")};
|
||||
sendFinished := script.NewEvent("send finished", []*script.Event{recvNCS}, script.Send{msgChan, finished});
|
||||
recvFinished := script.NewEvent("recv finished", []*script.Event{sendFinished}, script.RecvMatch{writeChan, matchFinished});
|
||||
setCipher := script.NewEvent("set cipher", []*script.Event{sendFinished}, script.RecvMatch{writeChan, matchSetCipher});
|
||||
recvConnectionState := script.NewEvent("recv state", []*script.Event{sendFinished}, script.Recv{controlChan, ConnectionState{true, "TLS_RSA_WITH_RC4_128_SHA", 0}});
|
||||
|
||||
err := script.Perform(0, []*script.Event{sendHello, setVersion, recvHello, recvCert, recvDone, sendCKX, sendCCS, recvNCS, sendFinished, setCipher, recvConnectionState, recvFinished});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
var testCertificate = fromHex("3082025930820203a003020102020900c2ec326b95228959300d06092a864886f70d01010505003054310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c7464310d300b0603550403130474657374301e170d3039313032303232323434355a170d3130313032303232323434355a3054310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c7464310d300b0603550403130474657374305c300d06092a864886f70d0101010500034b003048024100b2990f49c47dfa8cd400ae6a4d1b8a3b6a13642b23f28b003bfb97790ade9a4cc82b8b2a81747ddec08b6296e53a08c331687ef25c4bf4936ba1c0e6041e9d150203010001a381b73081b4301d0603551d0e0416041478a06086837c9293a8c9b70c0bdabdb9d77eeedf3081840603551d23047d307b801478a06086837c9293a8c9b70c0bdabdb9d77eeedfa158a4563054310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c7464310d300b0603550403130474657374820900c2ec326b95228959300c0603551d13040530030101ff300d06092a864886f70d0101050500034100ac23761ae1349d85a439caad4d0b932b09ea96de1917c3e0507c446f4838cb3076fb4d431db8c1987e96f1d7a8a2054dea3a64ec99a3f0eda4d47a163bf1f6ac")
|
||||
|
||||
func bigFromString(s string) *big.Int {
|
||||
ret := new(big.Int);
|
||||
ret.SetString(s, 10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var testPrivateKey = &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: bigFromString("9353930466774385905609975137998169297361893554149986716853295022578535724979677252958524466350471210367835187480748268864277464700638583474144061408845077"),
|
||||
E: 65537,
|
||||
},
|
||||
D: bigFromString("7266398431328116344057699379749222532279343923819063639497049039389899328538543087657733766554155839834519529439851673014800261285757759040931985506583861"),
|
||||
P: bigFromString("98920366548084643601728869055592650835572950932266967461790948584315647051443"),
|
||||
Q: bigFromString("94560208308847015747498523884063394671606671904944666360068158221458669711639"),
|
||||
}
|
292
record_process.go
Normal file
292
record_process.go
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright 2009 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 tls
|
||||
|
||||
// A recordProcessor accepts reassembled records, decrypts and verifies them
|
||||
// and routes them either to the handshake processor, to up to the application.
|
||||
// It also accepts requests from the application for the current connection
|
||||
// state, or for a notification when the state changes.
|
||||
|
||||
import (
|
||||
"bytes";
|
||||
"container/list";
|
||||
"crypto/subtle";
|
||||
"hash";
|
||||
)
|
||||
|
||||
// getConnectionState is a request from the application to get the current
|
||||
// ConnectionState.
|
||||
type getConnectionState struct {
|
||||
reply chan<- ConnectionState;
|
||||
}
|
||||
|
||||
// waitConnectionState is a request from the application to be notified when
|
||||
// the connection state changes.
|
||||
type waitConnectionState struct {
|
||||
reply chan<- ConnectionState;
|
||||
}
|
||||
|
||||
// connectionStateChange is a message from the handshake processor that the
|
||||
// connection state has changed.
|
||||
type connectionStateChange struct {
|
||||
connState ConnectionState;
|
||||
}
|
||||
|
||||
// changeCipherSpec is a message send to the handshake processor to signal that
|
||||
// the peer is switching ciphers.
|
||||
type changeCipherSpec struct{}
|
||||
|
||||
// newCipherSpec is a message from the handshake processor that future
|
||||
// records should be processed with a new cipher and MAC function.
|
||||
type newCipherSpec struct {
|
||||
encrypt encryptor;
|
||||
mac hash.Hash;
|
||||
}
|
||||
|
||||
type recordProcessor struct {
|
||||
decrypt encryptor;
|
||||
mac hash.Hash;
|
||||
seqNum uint64;
|
||||
handshakeBuf []byte;
|
||||
appDataChan chan<- []byte;
|
||||
requestChan <-chan interface{};
|
||||
controlChan <-chan interface{};
|
||||
recordChan <-chan *record;
|
||||
handshakeChan chan<- interface{};
|
||||
|
||||
// recordRead is nil when we don't wish to read any more.
|
||||
recordRead <-chan *record;
|
||||
// appDataSend is nil when len(appData) == 0.
|
||||
appDataSend chan<- []byte;
|
||||
// appData contains any application data queued for upstream.
|
||||
appData []byte;
|
||||
// A list of channels waiting for connState to change.
|
||||
waitQueue *list.List;
|
||||
connState ConnectionState;
|
||||
shutdown bool;
|
||||
header [13]byte;
|
||||
}
|
||||
|
||||
// drainRequestChannel processes messages from the request channel until it's closed.
|
||||
func drainRequestChannel(requestChan <-chan interface{}, c ConnectionState) {
|
||||
for v := range requestChan {
|
||||
if closed(requestChan) {
|
||||
return;
|
||||
}
|
||||
switch r := v.(type) {
|
||||
case getConnectionState:
|
||||
r.reply <- c;
|
||||
case waitConnectionState:
|
||||
r.reply <- c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *recordProcessor) loop(appDataChan chan<- []byte, requestChan <-chan interface{}, controlChan <-chan interface{}, recordChan <-chan *record, handshakeChan chan<- interface{}) {
|
||||
noop := nop{};
|
||||
p.decrypt = noop;
|
||||
p.mac = noop;
|
||||
p.waitQueue = list.New();
|
||||
|
||||
p.appDataChan = appDataChan;
|
||||
p.requestChan = requestChan;
|
||||
p.controlChan = controlChan;
|
||||
p.recordChan = recordChan;
|
||||
p.handshakeChan = handshakeChan;
|
||||
p.recordRead = recordChan;
|
||||
|
||||
for !p.shutdown {
|
||||
select {
|
||||
case p.appDataSend <- p.appData:
|
||||
p.appData = nil;
|
||||
p.appDataSend = nil;
|
||||
p.recordRead = p.recordChan;
|
||||
case c := <-controlChan:
|
||||
p.processControlMsg(c);
|
||||
case r := <-requestChan:
|
||||
p.processRequestMsg(r);
|
||||
case r := <-p.recordRead:
|
||||
p.processRecord(r);
|
||||
}
|
||||
}
|
||||
|
||||
p.wakeWaiters();
|
||||
go drainRequestChannel(p.requestChan, p.connState);
|
||||
go func() { for _ = range controlChan {} }();
|
||||
|
||||
close(handshakeChan);
|
||||
if len(p.appData) > 0 {
|
||||
appDataChan <- p.appData;
|
||||
}
|
||||
close(appDataChan);
|
||||
}
|
||||
|
||||
func (p *recordProcessor) processRequestMsg(requestMsg interface{}) {
|
||||
if closed(p.requestChan) {
|
||||
p.shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch r := requestMsg.(type) {
|
||||
case getConnectionState:
|
||||
r.reply <- p.connState;
|
||||
case waitConnectionState:
|
||||
if p.connState.HandshakeComplete {
|
||||
r.reply <- p.connState;
|
||||
}
|
||||
p.waitQueue.PushBack(r.reply);
|
||||
}
|
||||
}
|
||||
|
||||
func (p *recordProcessor) processControlMsg(msg interface{}) {
|
||||
connState, ok := msg.(ConnectionState);
|
||||
if !ok || closed(p.controlChan) {
|
||||
p.shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
p.connState = connState;
|
||||
p.wakeWaiters();
|
||||
}
|
||||
|
||||
func (p *recordProcessor) wakeWaiters() {
|
||||
for i := p.waitQueue.Front(); i != nil; i = i.Next() {
|
||||
i.Value.(chan<- ConnectionState) <- p.connState;
|
||||
}
|
||||
p.waitQueue.Init();
|
||||
}
|
||||
|
||||
func (p *recordProcessor) processRecord(r *record) {
|
||||
if closed(p.recordChan) {
|
||||
p.shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
p.decrypt.XORKeyStream(r.payload);
|
||||
if len(r.payload) < p.mac.Size() {
|
||||
p.error(alertBadRecordMAC);
|
||||
return;
|
||||
}
|
||||
|
||||
fillMACHeader(&p.header, p.seqNum, len(r.payload) - p.mac.Size(), r);
|
||||
p.seqNum++;
|
||||
|
||||
p.mac.Reset();
|
||||
p.mac.Write(p.header[0:13]);
|
||||
p.mac.Write(r.payload[0 : len(r.payload) - p.mac.Size()]);
|
||||
macBytes := p.mac.Sum();
|
||||
|
||||
if subtle.ConstantTimeCompare(macBytes, r.payload[len(r.payload) - p.mac.Size() : len(r.payload)]) != 1 {
|
||||
p.error(alertBadRecordMAC);
|
||||
return;
|
||||
}
|
||||
|
||||
switch r.contentType {
|
||||
case recordTypeHandshake:
|
||||
p.processHandshakeRecord(r.payload[0 : len(r.payload) - p.mac.Size()]);
|
||||
case recordTypeChangeCipherSpec:
|
||||
if len(r.payload) != 1 || r.payload[0] != 1 {
|
||||
p.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
p.handshakeChan <- changeCipherSpec{};
|
||||
newSpec, ok := (<-p.controlChan).(*newCipherSpec);
|
||||
if !ok {
|
||||
p.connState.Error = alertUnexpectedMessage;
|
||||
p.shutdown = true;
|
||||
return;
|
||||
}
|
||||
p.decrypt = newSpec.encrypt;
|
||||
p.mac = newSpec.mac;
|
||||
p.seqNum = 0;
|
||||
case recordTypeApplicationData:
|
||||
if p.connState.HandshakeComplete == false {
|
||||
p.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
p.recordRead = nil;
|
||||
p.appData = r.payload;
|
||||
p.appDataSend = p.appDataChan;
|
||||
default:
|
||||
p.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func (p *recordProcessor) processHandshakeRecord(data []byte) {
|
||||
if p.handshakeBuf == nil {
|
||||
p.handshakeBuf = data;
|
||||
} else {
|
||||
if len(p.handshakeBuf) > maxHandshakeMsg {
|
||||
p.error(alertInternalError);
|
||||
return;
|
||||
}
|
||||
newBuf := make([]byte, len(p.handshakeBuf)+len(data));
|
||||
bytes.Copy(newBuf, p.handshakeBuf);
|
||||
bytes.Copy(newBuf[len(p.handshakeBuf):len(newBuf)], data);
|
||||
p.handshakeBuf = newBuf;
|
||||
}
|
||||
|
||||
for len(p.handshakeBuf) >= 4 {
|
||||
handshakeLen := int(p.handshakeBuf[1])<<16 |
|
||||
int(p.handshakeBuf[2])<<8 |
|
||||
int(p.handshakeBuf[3]);
|
||||
if handshakeLen + 4 > len(p.handshakeBuf) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes := p.handshakeBuf[0 : handshakeLen + 4];
|
||||
p.handshakeBuf = p.handshakeBuf[handshakeLen + 4 : len(p.handshakeBuf)];
|
||||
if bytes[0] == typeFinished {
|
||||
// Special case because Finished is synchronous: the
|
||||
// handshake handler has to tell us if it's ok to start
|
||||
// forwarding application data.
|
||||
m := new(finishedMsg);
|
||||
if !m.unmarshal(bytes) {
|
||||
p.error(alertUnexpectedMessage);
|
||||
}
|
||||
p.handshakeChan <- m;
|
||||
var ok bool;
|
||||
p.connState, ok = (<-p.controlChan).(ConnectionState);
|
||||
if !ok || p.connState.Error != 0 {
|
||||
p.shutdown = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
msg, ok := parseHandshakeMsg(bytes);
|
||||
if !ok {
|
||||
p.error(alertUnexpectedMessage);
|
||||
return;
|
||||
}
|
||||
p.handshakeChan <- msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *recordProcessor) error(err alertType) {
|
||||
close(p.handshakeChan);
|
||||
p.connState.Error = err;
|
||||
p.wakeWaiters();
|
||||
p.shutdown = true;
|
||||
}
|
||||
|
||||
func parseHandshakeMsg(data []byte) (interface{}, bool) {
|
||||
var m interface {
|
||||
unmarshal([]byte) bool;
|
||||
}
|
||||
|
||||
switch data[0] {
|
||||
case typeClientHello:
|
||||
m = new(clientHelloMsg);
|
||||
case typeClientKeyExchange:
|
||||
m = new(clientKeyExchangeMsg);
|
||||
default:
|
||||
return nil, false;
|
||||
}
|
||||
|
||||
ok := m.unmarshal(data);
|
||||
return m, ok;
|
||||
}
|
137
record_process_test.go
Normal file
137
record_process_test.go
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2009 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 tls
|
||||
|
||||
import (
|
||||
"encoding/hex";
|
||||
"testing";
|
||||
"testing/script";
|
||||
)
|
||||
|
||||
func setup() (appDataChan chan []byte, requestChan chan interface{}, controlChan chan interface{}, recordChan chan *record, handshakeChan chan interface{}) {
|
||||
rp := new(recordProcessor);
|
||||
appDataChan = make(chan []byte);
|
||||
requestChan = make(chan interface{});
|
||||
controlChan = make(chan interface{});
|
||||
recordChan = make(chan *record);
|
||||
handshakeChan = make(chan interface{});
|
||||
|
||||
go rp.loop(appDataChan, requestChan, controlChan, recordChan, handshakeChan);
|
||||
return;
|
||||
}
|
||||
|
||||
func fromHex(s string) []byte {
|
||||
b, _ := hex.DecodeString(s);
|
||||
return b;
|
||||
}
|
||||
|
||||
func TestNullConnectionState(t *testing.T) {
|
||||
_, requestChan, controlChan, recordChan, _ := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
// Test a simple request for the connection state.
|
||||
replyChan := make(chan ConnectionState);
|
||||
sendReq := script.NewEvent("send request", nil, script.Send{requestChan, getConnectionState{replyChan}});
|
||||
getReply := script.NewEvent("get reply", []*script.Event{sendReq}, script.Recv{replyChan, ConnectionState{false, "", 0}});
|
||||
|
||||
err := script.Perform(0, []*script.Event{sendReq, getReply});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitConnectionState(t *testing.T) {
|
||||
_, requestChan, controlChan, recordChan, _ := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
// Test that waitConnectionState doesn't get a reply until the connection state changes.
|
||||
replyChan := make(chan ConnectionState);
|
||||
sendReq := script.NewEvent("send request", nil, script.Send{requestChan, waitConnectionState{replyChan}});
|
||||
replyChan2 := make(chan ConnectionState);
|
||||
sendReq2 := script.NewEvent("send request 2", []*script.Event{sendReq}, script.Send{requestChan, getConnectionState{replyChan2}});
|
||||
getReply2 := script.NewEvent("get reply 2", []*script.Event{sendReq2}, script.Recv{replyChan2, ConnectionState{false, "", 0}});
|
||||
sendState := script.NewEvent("send state", []*script.Event{getReply2}, script.Send{controlChan, ConnectionState{true, "test", 1}});
|
||||
getReply := script.NewEvent("get reply", []*script.Event{sendState}, script.Recv{replyChan, ConnectionState{true, "test", 1}});
|
||||
|
||||
err := script.Perform(0, []*script.Event{sendReq, sendReq2, getReply2, sendState, getReply});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakeAssembly(t *testing.T) {
|
||||
_, requestChan, controlChan, recordChan, handshakeChan := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
// Test the reassembly of a fragmented handshake message.
|
||||
send1 := script.NewEvent("send 1", nil, script.Send{recordChan, &record{recordTypeHandshake, 0, 0, fromHex("10000003")}});
|
||||
send2 := script.NewEvent("send 2", []*script.Event{send1}, script.Send{recordChan, &record{recordTypeHandshake, 0, 0, fromHex("0001")}});
|
||||
send3 := script.NewEvent("send 3", []*script.Event{send2}, script.Send{recordChan, &record{recordTypeHandshake, 0, 0, fromHex("42")}});
|
||||
recvMsg := script.NewEvent("recv", []*script.Event{send3}, script.Recv{handshakeChan, &clientKeyExchangeMsg{fromHex("10000003000142"), fromHex("42")}});
|
||||
|
||||
err := script.Perform(0, []*script.Event{send1, send2, send3, recvMsg});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestEarlyApplicationData(t *testing.T) {
|
||||
_, requestChan, controlChan, recordChan, handshakeChan := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
// Test that applicaton data received before the handshake has completed results in an error.
|
||||
send := script.NewEvent("send", nil, script.Send{recordChan, &record{recordTypeApplicationData, 0, 0, fromHex("")}});
|
||||
recv := script.NewEvent("recv", []*script.Event{send}, script.Closed{handshakeChan});
|
||||
|
||||
err := script.Perform(0, []*script.Event{send, recv});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationData(t *testing.T) {
|
||||
appDataChan, requestChan, controlChan, recordChan, handshakeChan := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
// Test that the application data is forwarded after a successful Finished message.
|
||||
send1 := script.NewEvent("send 1", nil, script.Send{recordChan, &record{recordTypeHandshake, 0, 0, fromHex("1400000c000000000000000000000000")}});
|
||||
recv1 := script.NewEvent("recv finished", []*script.Event{send1}, script.Recv{handshakeChan, &finishedMsg{fromHex("1400000c000000000000000000000000"), fromHex("000000000000000000000000")}});
|
||||
send2 := script.NewEvent("send connState", []*script.Event{recv1}, script.Send{controlChan, ConnectionState{true, "", 0}});
|
||||
send3 := script.NewEvent("send 2", []*script.Event{send2}, script.Send{recordChan, &record{recordTypeApplicationData, 0, 0, fromHex("0102")}});
|
||||
recv2 := script.NewEvent("recv data", []*script.Event{send3}, script.Recv{appDataChan, []byte{0x01, 0x02}});
|
||||
|
||||
err := script.Perform(0, []*script.Event{send1, recv1, send2, send3, recv2});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidChangeCipherSpec(t *testing.T) {
|
||||
appDataChan, requestChan, controlChan, recordChan, handshakeChan := setup();
|
||||
defer close(requestChan);
|
||||
defer close(controlChan);
|
||||
defer close(recordChan);
|
||||
|
||||
send1 := script.NewEvent("send 1", nil, script.Send{recordChan, &record{recordTypeChangeCipherSpec, 0, 0, []byte{1}}});
|
||||
recv1 := script.NewEvent("recv 1", []*script.Event{send1}, script.Recv{handshakeChan, changeCipherSpec{}});
|
||||
send2 := script.NewEvent("send 2", []*script.Event{recv1}, script.Send{controlChan, ConnectionState{false, "", 42}});
|
||||
close := script.NewEvent("close 1", []*script.Event{send2}, script.Closed{appDataChan});
|
||||
close2 := script.NewEvent("close 2", []*script.Event{send2}, script.Closed{handshakeChan});
|
||||
|
||||
err := script.Perform(0, []*script.Event{send1, recv1, send2, close, close2});
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %s", err);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user