// 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 ( "fmt" "hash" "io" ) // writerEnableApplicationData is a message which instructs recordWriter to // start reading and transmitting data from the application data channel. type writerEnableApplicationData struct{} // writerChangeCipherSpec updates the encryption and MAC functions and resets // the sequence count. type writerChangeCipherSpec struct { encryptor encryptor mac hash.Hash } // writerSetVersion sets the version number bytes that we included in the // record header for future records. type writerSetVersion struct { major, minor uint8 } // A recordWriter accepts messages from the handshake processor and // application data. It writes them to the outgoing connection and blocks on // writing. It doesn't read from the application data channel until the // handshake processor has signaled that the handshake is complete. type recordWriter struct { writer io.Writer encryptor encryptor mac hash.Hash seqNum uint64 major, minor uint8 shutdown bool appChan <-chan []byte controlChan <-chan interface{} header [13]byte } func (w *recordWriter) loop(writer io.Writer, appChan <-chan []byte, controlChan <-chan interface{}) { w.writer = writer w.encryptor = nop{} w.mac = nop{} w.appChan = appChan w.controlChan = controlChan for !w.shutdown { msg := <-controlChan if _, ok := msg.(writerEnableApplicationData); ok { break } w.processControlMessage(msg) } for !w.shutdown { // Always process control messages first. if controlMsg, ok := <-controlChan; ok { w.processControlMessage(controlMsg) continue } select { case controlMsg := <-controlChan: w.processControlMessage(controlMsg) case appMsg := <-appChan: w.processAppMessage(appMsg) } } if !closed(appChan) { go func() { for _ = range appChan { } }() } if !closed(controlChan) { go func() { for _ = range controlChan { } }() } } // fillMACHeader generates a MAC header. See RFC 4346, section 6.2.3.1. func fillMACHeader(header *[13]byte, seqNum uint64, length int, r *record) { header[0] = uint8(seqNum >> 56) header[1] = uint8(seqNum >> 48) header[2] = uint8(seqNum >> 40) header[3] = uint8(seqNum >> 32) header[4] = uint8(seqNum >> 24) header[5] = uint8(seqNum >> 16) header[6] = uint8(seqNum >> 8) header[7] = uint8(seqNum) header[8] = uint8(r.contentType) header[9] = r.major header[10] = r.minor header[11] = uint8(length >> 8) header[12] = uint8(length) } func (w *recordWriter) writeRecord(r *record) { w.mac.Reset() fillMACHeader(&w.header, w.seqNum, len(r.payload), r) w.mac.Write(w.header[0:13]) w.mac.Write(r.payload) macBytes := w.mac.Sum() w.encryptor.XORKeyStream(r.payload) w.encryptor.XORKeyStream(macBytes) length := len(r.payload) + len(macBytes) w.header[11] = uint8(length >> 8) w.header[12] = uint8(length) w.writer.Write(w.header[8:13]) w.writer.Write(r.payload) w.writer.Write(macBytes) w.seqNum++ } func (w *recordWriter) processControlMessage(controlMsg interface{}) { if controlMsg == nil { w.shutdown = true return } switch msg := controlMsg.(type) { case writerChangeCipherSpec: w.writeRecord(&record{recordTypeChangeCipherSpec, w.major, w.minor, []byte{0x01}}) w.encryptor = msg.encryptor w.mac = msg.mac w.seqNum = 0 case writerSetVersion: w.major = msg.major w.minor = msg.minor case alert: w.writeRecord(&record{recordTypeAlert, w.major, w.minor, []byte{byte(msg.level), byte(msg.error)}}) case handshakeMessage: // TODO(agl): marshal may return a slice too large for a single record. w.writeRecord(&record{recordTypeHandshake, w.major, w.minor, msg.marshal()}) default: fmt.Printf("processControlMessage: unknown %#v\n", msg) } } func (w *recordWriter) processAppMessage(appMsg []byte) { if closed(w.appChan) { w.writeRecord(&record{recordTypeApplicationData, w.major, w.minor, []byte{byte(alertCloseNotify)}}) w.shutdown = true return } var done int for done < len(appMsg) { todo := len(appMsg) if todo > maxTLSPlaintext { todo = maxTLSPlaintext } w.writeRecord(&record{recordTypeApplicationData, w.major, w.minor, appMsg[done : done+todo]}) done += todo } }