Mirror of BoringSSL (grpc依赖)
https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
15 KiB
502 lines
15 KiB
// Copyright 2014 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. |
|
|
|
// DTLS implementation. |
|
// |
|
// NOTE: This is a not even a remotely production-quality DTLS |
|
// implementation. It is the bare minimum necessary to be able to |
|
// achieve coverage on BoringSSL's implementation. Of note is that |
|
// this implementation assumes the underlying net.PacketConn is not |
|
// only reliable but also ordered. BoringSSL will be expected to deal |
|
// with simulated loss, but there is no point in forcing the test |
|
// driver to. |
|
|
|
package runner |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"math/rand" |
|
"net" |
|
) |
|
|
|
func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) { |
|
recordHeaderLen := dtlsRecordHeaderLen |
|
|
|
if c.rawInput == nil { |
|
c.rawInput = c.in.newBlock() |
|
} |
|
b := c.rawInput |
|
|
|
// Read a new packet only if the current one is empty. |
|
var newPacket bool |
|
if len(b.data) == 0 { |
|
// Pick some absurdly large buffer size. |
|
b.resize(maxCiphertext + recordHeaderLen) |
|
n, err := c.conn.Read(c.rawInput.data) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
if c.config.Bugs.MaxPacketLength != 0 && n > c.config.Bugs.MaxPacketLength { |
|
return 0, nil, fmt.Errorf("dtls: exceeded maximum packet length") |
|
} |
|
c.rawInput.resize(n) |
|
newPacket = true |
|
} |
|
|
|
// Read out one record. |
|
// |
|
// A real DTLS implementation should be tolerant of errors, |
|
// but this is test code. We should not be tolerant of our |
|
// peer sending garbage. |
|
if len(b.data) < recordHeaderLen { |
|
return 0, nil, errors.New("dtls: failed to read record header") |
|
} |
|
typ := recordType(b.data[0]) |
|
vers := uint16(b.data[1])<<8 | uint16(b.data[2]) |
|
// Alerts sent near version negotiation do not have a well-defined |
|
// record-layer version prior to TLS 1.3. (In TLS 1.3, the record-layer |
|
// version is irrelevant.) |
|
if typ != recordTypeAlert { |
|
if c.haveVers { |
|
if vers != c.wireVersion { |
|
c.sendAlert(alertProtocolVersion) |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: received record with version %x when expecting version %x", vers, c.wireVersion)) |
|
} |
|
} else { |
|
// Pre-version-negotiation alerts may be sent with any version. |
|
if expect := c.config.Bugs.ExpectInitialRecordVersion; expect != 0 && vers != expect { |
|
c.sendAlert(alertProtocolVersion) |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: received record with version %x when expecting version %x", vers, expect)) |
|
} |
|
} |
|
} |
|
epoch := b.data[3:5] |
|
seq := b.data[5:11] |
|
// For test purposes, require the sequence number be monotonically |
|
// increasing, so c.in includes the minimum next sequence number. Gaps |
|
// may occur if packets failed to be sent out. A real implementation |
|
// would maintain a replay window and such. |
|
if !bytes.Equal(epoch, c.in.seq[:2]) { |
|
c.sendAlert(alertIllegalParameter) |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad epoch")) |
|
} |
|
if bytes.Compare(seq, c.in.seq[2:]) < 0 { |
|
c.sendAlert(alertIllegalParameter) |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad sequence number")) |
|
} |
|
copy(c.in.seq[2:], seq) |
|
n := int(b.data[11])<<8 | int(b.data[12]) |
|
if n > maxCiphertext || len(b.data) < recordHeaderLen+n { |
|
c.sendAlert(alertRecordOverflow) |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: oversized record received with length %d", n)) |
|
} |
|
|
|
// Process message. |
|
b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n) |
|
ok, off, _, alertValue := c.in.decrypt(b) |
|
if !ok { |
|
// A real DTLS implementation would silently ignore bad records, |
|
// but we want to notice errors from the implementation under |
|
// test. |
|
return 0, nil, c.in.setErrorLocked(c.sendAlert(alertValue)) |
|
} |
|
b.off = off |
|
|
|
// TODO(nharper): Once DTLS 1.3 is defined, handle the extra |
|
// parameter from decrypt. |
|
|
|
// Require that ChangeCipherSpec always share a packet with either the |
|
// previous or next handshake message. |
|
if newPacket && typ == recordTypeChangeCipherSpec && c.rawInput == nil { |
|
return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: ChangeCipherSpec not packed together with Finished")) |
|
} |
|
|
|
return typ, b, nil |
|
} |
|
|
|
func (c *Conn) makeFragment(header, data []byte, fragOffset, fragLen int) []byte { |
|
fragment := make([]byte, 0, 12+fragLen) |
|
fragment = append(fragment, header...) |
|
fragment = append(fragment, byte(c.sendHandshakeSeq>>8), byte(c.sendHandshakeSeq)) |
|
fragment = append(fragment, byte(fragOffset>>16), byte(fragOffset>>8), byte(fragOffset)) |
|
fragment = append(fragment, byte(fragLen>>16), byte(fragLen>>8), byte(fragLen)) |
|
fragment = append(fragment, data[fragOffset:fragOffset+fragLen]...) |
|
return fragment |
|
} |
|
|
|
func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) { |
|
// Only handshake messages are fragmented. |
|
if typ != recordTypeHandshake { |
|
reorder := typ == recordTypeChangeCipherSpec && c.config.Bugs.ReorderChangeCipherSpec |
|
|
|
// Flush pending handshake messages before encrypting a new record. |
|
if !reorder { |
|
err = c.dtlsPackHandshake() |
|
if err != nil { |
|
return |
|
} |
|
} |
|
|
|
if typ == recordTypeApplicationData && len(data) > 1 && c.config.Bugs.SplitAndPackAppData { |
|
_, err = c.dtlsPackRecord(typ, data[:len(data)/2], false) |
|
if err != nil { |
|
return |
|
} |
|
_, err = c.dtlsPackRecord(typ, data[len(data)/2:], true) |
|
if err != nil { |
|
return |
|
} |
|
n = len(data) |
|
} else { |
|
n, err = c.dtlsPackRecord(typ, data, false) |
|
if err != nil { |
|
return |
|
} |
|
} |
|
|
|
if reorder { |
|
err = c.dtlsPackHandshake() |
|
if err != nil { |
|
return |
|
} |
|
} |
|
|
|
if typ == recordTypeChangeCipherSpec { |
|
err = c.out.changeCipherSpec(c.config) |
|
if err != nil { |
|
return n, c.sendAlertLocked(alertLevelError, err.(alert)) |
|
} |
|
} else { |
|
// ChangeCipherSpec is part of the handshake and not |
|
// flushed until dtlsFlushPacket. |
|
err = c.dtlsFlushPacket() |
|
if err != nil { |
|
return |
|
} |
|
} |
|
return |
|
} |
|
|
|
if c.out.cipher == nil && c.config.Bugs.StrayChangeCipherSpec { |
|
_, err = c.dtlsPackRecord(recordTypeChangeCipherSpec, []byte{1}, false) |
|
if err != nil { |
|
return |
|
} |
|
} |
|
|
|
maxLen := c.config.Bugs.MaxHandshakeRecordLength |
|
if maxLen <= 0 { |
|
maxLen = 1024 |
|
} |
|
|
|
// Handshake messages have to be modified to include fragment |
|
// offset and length and with the header replicated. Save the |
|
// TLS header here. |
|
// |
|
// TODO(davidben): This assumes that data contains exactly one |
|
// handshake message. This is incompatible with |
|
// FragmentAcrossChangeCipherSpec. (Which is unfortunate |
|
// because OpenSSL's DTLS implementation will probably accept |
|
// such fragmentation and could do with a fix + tests.) |
|
header := data[:4] |
|
data = data[4:] |
|
|
|
isFinished := header[0] == typeFinished |
|
|
|
if c.config.Bugs.SendEmptyFragments { |
|
c.pendingFragments = append(c.pendingFragments, c.makeFragment(header, data, 0, 0)) |
|
c.pendingFragments = append(c.pendingFragments, c.makeFragment(header, data, len(data), 0)) |
|
} |
|
|
|
firstRun := true |
|
fragOffset := 0 |
|
for firstRun || fragOffset < len(data) { |
|
firstRun = false |
|
fragLen := len(data) - fragOffset |
|
if fragLen > maxLen { |
|
fragLen = maxLen |
|
} |
|
|
|
fragment := c.makeFragment(header, data, fragOffset, fragLen) |
|
if c.config.Bugs.FragmentMessageTypeMismatch && fragOffset > 0 { |
|
fragment[0]++ |
|
} |
|
if c.config.Bugs.FragmentMessageLengthMismatch && fragOffset > 0 { |
|
fragment[3]++ |
|
} |
|
|
|
// Buffer the fragment for later. They will be sent (and |
|
// reordered) on flush. |
|
c.pendingFragments = append(c.pendingFragments, fragment) |
|
if c.config.Bugs.ReorderHandshakeFragments { |
|
// Don't duplicate Finished to avoid the peer |
|
// interpreting it as a retransmit request. |
|
if !isFinished { |
|
c.pendingFragments = append(c.pendingFragments, fragment) |
|
} |
|
|
|
if fragLen > (maxLen+1)/2 { |
|
// Overlap each fragment by half. |
|
fragLen = (maxLen + 1) / 2 |
|
} |
|
} |
|
fragOffset += fragLen |
|
n += fragLen |
|
} |
|
shouldSendTwice := c.config.Bugs.MixCompleteMessageWithFragments |
|
if isFinished { |
|
shouldSendTwice = c.config.Bugs.RetransmitFinished |
|
} |
|
if shouldSendTwice { |
|
fragment := c.makeFragment(header, data, 0, len(data)) |
|
c.pendingFragments = append(c.pendingFragments, fragment) |
|
} |
|
|
|
// Increment the handshake sequence number for the next |
|
// handshake message. |
|
c.sendHandshakeSeq++ |
|
return |
|
} |
|
|
|
// dtlsPackHandshake packs the pending handshake flight into the pending |
|
// record. Callers should follow up with dtlsFlushPacket to write the packets. |
|
func (c *Conn) dtlsPackHandshake() error { |
|
// This is a test-only DTLS implementation, so there is no need to |
|
// retain |c.pendingFragments| for a future retransmit. |
|
var fragments [][]byte |
|
fragments, c.pendingFragments = c.pendingFragments, fragments |
|
|
|
if c.config.Bugs.ReorderHandshakeFragments { |
|
perm := rand.New(rand.NewSource(0)).Perm(len(fragments)) |
|
tmp := make([][]byte, len(fragments)) |
|
for i := range tmp { |
|
tmp[i] = fragments[perm[i]] |
|
} |
|
fragments = tmp |
|
} else if c.config.Bugs.ReverseHandshakeFragments { |
|
tmp := make([][]byte, len(fragments)) |
|
for i := range tmp { |
|
tmp[i] = fragments[len(fragments)-i-1] |
|
} |
|
fragments = tmp |
|
} |
|
|
|
maxRecordLen := c.config.Bugs.PackHandshakeFragments |
|
|
|
// Pack handshake fragments into records. |
|
var records [][]byte |
|
for _, fragment := range fragments { |
|
if n := c.config.Bugs.SplitFragments; n > 0 { |
|
if len(fragment) > n { |
|
records = append(records, fragment[:n]) |
|
records = append(records, fragment[n:]) |
|
} else { |
|
records = append(records, fragment) |
|
} |
|
} else if i := len(records) - 1; len(records) > 0 && len(records[i])+len(fragment) <= maxRecordLen { |
|
records[i] = append(records[i], fragment...) |
|
} else { |
|
// The fragment will be appended to, so copy it. |
|
records = append(records, append([]byte{}, fragment...)) |
|
} |
|
} |
|
|
|
// Send the records. |
|
for _, record := range records { |
|
_, err := c.dtlsPackRecord(recordTypeHandshake, record, false) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (c *Conn) dtlsFlushHandshake() error { |
|
if err := c.dtlsPackHandshake(); err != nil { |
|
return err |
|
} |
|
if err := c.dtlsFlushPacket(); err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// dtlsPackRecord packs a single record to the pending packet, flushing it |
|
// if necessary. The caller should call dtlsFlushPacket to flush the current |
|
// pending packet afterwards. |
|
func (c *Conn) dtlsPackRecord(typ recordType, data []byte, mustPack bool) (n int, err error) { |
|
recordHeaderLen := dtlsRecordHeaderLen |
|
maxLen := c.config.Bugs.MaxHandshakeRecordLength |
|
if maxLen <= 0 { |
|
maxLen = 1024 |
|
} |
|
|
|
b := c.out.newBlock() |
|
|
|
explicitIVLen := 0 |
|
explicitIVIsSeq := false |
|
|
|
if cbc, ok := c.out.cipher.(cbcMode); ok { |
|
// Block cipher modes have an explicit IV. |
|
explicitIVLen = cbc.BlockSize() |
|
} else if aead, ok := c.out.cipher.(*tlsAead); ok { |
|
if aead.explicitNonce { |
|
explicitIVLen = 8 |
|
// The AES-GCM construction in TLS has an explicit nonce so that |
|
// the nonce can be random. However, the nonce is only 8 bytes |
|
// which is too small for a secure, random nonce. Therefore we |
|
// use the sequence number as the nonce. |
|
explicitIVIsSeq = true |
|
} |
|
} else if _, ok := c.out.cipher.(nullCipher); !ok && c.out.cipher != nil { |
|
panic("Unknown cipher") |
|
} |
|
b.resize(recordHeaderLen + explicitIVLen + len(data)) |
|
// TODO(nharper): DTLS 1.3 will likely need to set this to |
|
// recordTypeApplicationData if c.out.cipher != nil. |
|
b.data[0] = byte(typ) |
|
vers := c.wireVersion |
|
if vers == 0 { |
|
// Some TLS servers fail if the record version is greater than |
|
// TLS 1.0 for the initial ClientHello. |
|
if c.isDTLS { |
|
vers = VersionDTLS10 |
|
} else { |
|
vers = VersionTLS10 |
|
} |
|
} |
|
b.data[1] = byte(vers >> 8) |
|
b.data[2] = byte(vers) |
|
// DTLS records include an explicit sequence number. |
|
copy(b.data[3:11], c.out.outSeq[0:]) |
|
b.data[11] = byte(len(data) >> 8) |
|
b.data[12] = byte(len(data)) |
|
if explicitIVLen > 0 { |
|
explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen] |
|
if explicitIVIsSeq { |
|
copy(explicitIV, c.out.outSeq[:]) |
|
} else { |
|
if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil { |
|
return |
|
} |
|
} |
|
} |
|
copy(b.data[recordHeaderLen+explicitIVLen:], data) |
|
c.out.encrypt(b, explicitIVLen, typ) |
|
|
|
// Flush the current pending packet if necessary. |
|
if !mustPack && len(b.data)+len(c.pendingPacket) > c.config.Bugs.PackHandshakeRecords { |
|
err = c.dtlsFlushPacket() |
|
if err != nil { |
|
c.out.freeBlock(b) |
|
return |
|
} |
|
} |
|
|
|
// Add the record to the pending packet. |
|
c.pendingPacket = append(c.pendingPacket, b.data...) |
|
c.out.freeBlock(b) |
|
n = len(data) |
|
return |
|
} |
|
|
|
func (c *Conn) dtlsFlushPacket() error { |
|
if len(c.pendingPacket) == 0 { |
|
return nil |
|
} |
|
_, err := c.conn.Write(c.pendingPacket) |
|
c.pendingPacket = nil |
|
return err |
|
} |
|
|
|
func (c *Conn) dtlsDoReadHandshake() ([]byte, error) { |
|
// Assemble a full handshake message. For test purposes, this |
|
// implementation assumes fragments arrive in order. It may |
|
// need to be cleverer if we ever test BoringSSL's retransmit |
|
// behavior. |
|
for len(c.handMsg) < 4+c.handMsgLen { |
|
// Get a new handshake record if the previous has been |
|
// exhausted. |
|
if c.hand.Len() == 0 { |
|
if err := c.in.err; err != nil { |
|
return nil, err |
|
} |
|
if err := c.readRecord(recordTypeHandshake); err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
// Read the next fragment. It must fit entirely within |
|
// the record. |
|
if c.hand.Len() < 12 { |
|
return nil, errors.New("dtls: bad handshake record") |
|
} |
|
header := c.hand.Next(12) |
|
fragN := int(header[1])<<16 | int(header[2])<<8 | int(header[3]) |
|
fragSeq := uint16(header[4])<<8 | uint16(header[5]) |
|
fragOff := int(header[6])<<16 | int(header[7])<<8 | int(header[8]) |
|
fragLen := int(header[9])<<16 | int(header[10])<<8 | int(header[11]) |
|
|
|
if c.hand.Len() < fragLen { |
|
return nil, errors.New("dtls: fragment length too long") |
|
} |
|
fragment := c.hand.Next(fragLen) |
|
|
|
// Check it's a fragment for the right message. |
|
if fragSeq != c.recvHandshakeSeq { |
|
return nil, errors.New("dtls: bad handshake sequence number") |
|
} |
|
|
|
// Check that the length is consistent. |
|
if c.handMsg == nil { |
|
c.handMsgLen = fragN |
|
if c.handMsgLen > maxHandshake { |
|
return nil, c.in.setErrorLocked(c.sendAlert(alertInternalError)) |
|
} |
|
// Start with the TLS handshake header, |
|
// without the DTLS bits. |
|
c.handMsg = append([]byte{}, header[:4]...) |
|
} else if fragN != c.handMsgLen { |
|
return nil, errors.New("dtls: bad handshake length") |
|
} |
|
|
|
// Add the fragment to the pending message. |
|
if 4+fragOff != len(c.handMsg) { |
|
return nil, errors.New("dtls: bad fragment offset") |
|
} |
|
if fragOff+fragLen > c.handMsgLen { |
|
return nil, errors.New("dtls: bad fragment length") |
|
} |
|
c.handMsg = append(c.handMsg, fragment...) |
|
} |
|
c.recvHandshakeSeq++ |
|
ret := c.handMsg |
|
c.handMsg, c.handMsgLen = nil, 0 |
|
return ret, nil |
|
} |
|
|
|
// DTLSServer returns a new DTLS server side connection |
|
// using conn as the underlying transport. |
|
// The configuration config must be non-nil and must have |
|
// at least one certificate. |
|
func DTLSServer(conn net.Conn, config *Config) *Conn { |
|
c := &Conn{config: config, isDTLS: true, conn: conn} |
|
c.init() |
|
return c |
|
} |
|
|
|
// DTLSClient returns a new DTLS client side connection |
|
// using conn as the underlying transport. |
|
// The config cannot be nil: users must set either ServerHostname or |
|
// InsecureSkipVerify in the config. |
|
func DTLSClient(conn net.Conn, config *Config) *Conn { |
|
c := &Conn{config: config, isClient: true, isDTLS: true, conn: conn} |
|
c.init() |
|
return c |
|
}
|
|
|