mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-03 20:17:36 +03:00
Message marshalling makes use of BytesOrPanic a lot, under the
assumption that it will never panic. This assumption was incorrect, and
specifically crafted handshakes could trigger panics. Rather than just
surgically replacing the usages of BytesOrPanic in paths that could
panic, replace all usages of it with proper error returns in case there
are other ways of triggering panics which we didn't find.
In one specific case, the tree routed by expandLabel, we replace the
usage of BytesOrPanic, but retain a panic. This function already
explicitly panicked elsewhere, and returning an error from it becomes
rather painful because it requires changing a large number of APIs.
The marshalling is unlikely to ever panic, as the inputs are all either
fixed length, or already limited to the sizes required. If it were to
panic, it'd likely only be during development. A close inspection shows
no paths for a user to cause a panic currently.
This patches ends up being rather large, since it requires routing
errors back through functions which previously had no error returns.
Where possible I've tried to use helpers that reduce the verbosity
of frequently repeated stanzas, and to make the diffs as minimal as
possible.
Thanks to Marten Seemann for reporting this issue.
Fixes #58001
Fixes CVE-2022-41724
Change-Id: Ieb55867ef0a3e1e867b33f09421932510cb58851
Reviewed-on: 1679436
Reviewed-by: Julie Qiu <julieqiu@google.com>
TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com>
Run-TryBot: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/468125
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
// Copyright 2012 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"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"io"
|
|
|
|
"golang.org/x/crypto/cryptobyte"
|
|
)
|
|
|
|
// sessionState contains the information that is serialized into a session
|
|
// ticket in order to later resume a connection.
|
|
type sessionState struct {
|
|
vers uint16
|
|
cipherSuite uint16
|
|
createdAt uint64
|
|
masterSecret []byte // opaque master_secret<1..2^16-1>;
|
|
// struct { opaque certificate<1..2^24-1> } Certificate;
|
|
certificates [][]byte // Certificate certificate_list<0..2^24-1>;
|
|
|
|
// usedOldKey is true if the ticket from which this session came from
|
|
// was encrypted with an older key and thus should be refreshed.
|
|
usedOldKey bool
|
|
}
|
|
|
|
func (m *sessionState) marshal() ([]byte, error) {
|
|
var b cryptobyte.Builder
|
|
b.AddUint16(m.vers)
|
|
b.AddUint16(m.cipherSuite)
|
|
addUint64(&b, m.createdAt)
|
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
b.AddBytes(m.masterSecret)
|
|
})
|
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
for _, cert := range m.certificates {
|
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
b.AddBytes(cert)
|
|
})
|
|
}
|
|
})
|
|
return b.Bytes()
|
|
}
|
|
|
|
func (m *sessionState) unmarshal(data []byte) bool {
|
|
*m = sessionState{usedOldKey: m.usedOldKey}
|
|
s := cryptobyte.String(data)
|
|
if ok := s.ReadUint16(&m.vers) &&
|
|
s.ReadUint16(&m.cipherSuite) &&
|
|
readUint64(&s, &m.createdAt) &&
|
|
readUint16LengthPrefixed(&s, &m.masterSecret) &&
|
|
len(m.masterSecret) != 0; !ok {
|
|
return false
|
|
}
|
|
var certList cryptobyte.String
|
|
if !s.ReadUint24LengthPrefixed(&certList) {
|
|
return false
|
|
}
|
|
for !certList.Empty() {
|
|
var cert []byte
|
|
if !readUint24LengthPrefixed(&certList, &cert) {
|
|
return false
|
|
}
|
|
m.certificates = append(m.certificates, cert)
|
|
}
|
|
return s.Empty()
|
|
}
|
|
|
|
// sessionStateTLS13 is the content of a TLS 1.3 session ticket. Its first
|
|
// version (revision = 0) doesn't carry any of the information needed for 0-RTT
|
|
// validation and the nonce is always empty.
|
|
type sessionStateTLS13 struct {
|
|
// uint8 version = 0x0304;
|
|
// uint8 revision = 0;
|
|
cipherSuite uint16
|
|
createdAt uint64
|
|
resumptionSecret []byte // opaque resumption_master_secret<1..2^8-1>;
|
|
certificate Certificate // CertificateEntry certificate_list<0..2^24-1>;
|
|
}
|
|
|
|
func (m *sessionStateTLS13) marshal() ([]byte, error) {
|
|
var b cryptobyte.Builder
|
|
b.AddUint16(VersionTLS13)
|
|
b.AddUint8(0) // revision
|
|
b.AddUint16(m.cipherSuite)
|
|
addUint64(&b, m.createdAt)
|
|
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
|
b.AddBytes(m.resumptionSecret)
|
|
})
|
|
marshalCertificate(&b, m.certificate)
|
|
return b.Bytes()
|
|
}
|
|
|
|
func (m *sessionStateTLS13) unmarshal(data []byte) bool {
|
|
*m = sessionStateTLS13{}
|
|
s := cryptobyte.String(data)
|
|
var version uint16
|
|
var revision uint8
|
|
return s.ReadUint16(&version) &&
|
|
version == VersionTLS13 &&
|
|
s.ReadUint8(&revision) &&
|
|
revision == 0 &&
|
|
s.ReadUint16(&m.cipherSuite) &&
|
|
readUint64(&s, &m.createdAt) &&
|
|
readUint8LengthPrefixed(&s, &m.resumptionSecret) &&
|
|
len(m.resumptionSecret) != 0 &&
|
|
unmarshalCertificate(&s, &m.certificate) &&
|
|
s.Empty()
|
|
}
|
|
|
|
func (c *Conn) encryptTicket(state []byte) ([]byte, error) {
|
|
if len(c.ticketKeys) == 0 {
|
|
return nil, errors.New("tls: internal error: session ticket keys unavailable")
|
|
}
|
|
|
|
encrypted := make([]byte, ticketKeyNameLen+aes.BlockSize+len(state)+sha256.Size)
|
|
keyName := encrypted[:ticketKeyNameLen]
|
|
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
|
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
|
|
|
if _, err := io.ReadFull(c.config.rand(), iv); err != nil {
|
|
return nil, err
|
|
}
|
|
key := c.ticketKeys[0]
|
|
copy(keyName, key.keyName[:])
|
|
block, err := aes.NewCipher(key.aesKey[:])
|
|
if err != nil {
|
|
return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error())
|
|
}
|
|
cipher.NewCTR(block, iv).XORKeyStream(encrypted[ticketKeyNameLen+aes.BlockSize:], state)
|
|
|
|
mac := hmac.New(sha256.New, key.hmacKey[:])
|
|
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
|
mac.Sum(macBytes[:0])
|
|
|
|
return encrypted, nil
|
|
}
|
|
|
|
func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey bool) {
|
|
if len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
|
|
return nil, false
|
|
}
|
|
|
|
keyName := encrypted[:ticketKeyNameLen]
|
|
iv := encrypted[ticketKeyNameLen : ticketKeyNameLen+aes.BlockSize]
|
|
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
|
ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size]
|
|
|
|
keyIndex := -1
|
|
for i, candidateKey := range c.ticketKeys {
|
|
if bytes.Equal(keyName, candidateKey.keyName[:]) {
|
|
keyIndex = i
|
|
break
|
|
}
|
|
}
|
|
if keyIndex == -1 {
|
|
return nil, false
|
|
}
|
|
key := &c.ticketKeys[keyIndex]
|
|
|
|
mac := hmac.New(sha256.New, key.hmacKey[:])
|
|
mac.Write(encrypted[:len(encrypted)-sha256.Size])
|
|
expected := mac.Sum(nil)
|
|
|
|
if subtle.ConstantTimeCompare(macBytes, expected) != 1 {
|
|
return nil, false
|
|
}
|
|
|
|
block, err := aes.NewCipher(key.aesKey[:])
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
plaintext = make([]byte, len(ciphertext))
|
|
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
|
|
|
|
return plaintext, keyIndex > 0
|
|
}
|