mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-03 03:57:36 +03:00
* sync: Go 1.21rc3, QUIC support added (#207) * sync: merge with upstream tag/go-1.21rc3 (#11) * fix: all tests pass * impl: UQUIC Transport * deps: bump up min Go version * new: uquic * fix: add QUICTransportParameter * deprecated: Go 1.19 no longer supported Go 1.19 will fail to build or pass the test once we bump up to the new version. * sync: crypto/tls: restrict RSA keys in certificates to <= 8192 bits (#209) * [release-branch.go1.21] crypto/tls: restrict RSA keys in certificates to <= 8192 bits Extremely large RSA keys in certificate chains can cause a client/server to expend significant CPU time verifying signatures. Limit this by restricting the size of RSA keys transmitted during handshakes to <= 8192 bits. Based on a survey of publicly trusted RSA keys, there are currently only three certificates in circulation with keys larger than this, and all three appear to be test certificates that are not actively deployed. It is possible there are larger keys in use in private PKIs, but we target the web PKI, so causing breakage here in the interests of increasing the default safety of users of crypto/tls seems reasonable. Thanks to Mateusz Poliwczak for reporting this issue. Fixes CVE-2023-29409 * build: [ci skip] boring not included * fix: typo [ci skip] * docs: replenish readme [ci skip] replace old build status badge with new ones, bump up required version noted in docs, update developer contact to reflect current status.
782 lines
23 KiB
Go
782 lines
23 KiB
Go
// Copyright 2017 Google Inc. 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
)
|
|
|
|
type UConn struct {
|
|
*Conn
|
|
|
|
Extensions []TLSExtension
|
|
ClientHelloID ClientHelloID
|
|
|
|
ClientHelloBuilt bool
|
|
HandshakeState PubClientHandshakeState
|
|
|
|
// sessionID may or may not depend on ticket; nil => random
|
|
GetSessionID func(ticket []byte) [32]byte
|
|
|
|
greaseSeed [ssl_grease_last_index]uint16
|
|
|
|
omitSNIExtension bool
|
|
|
|
// certCompressionAlgs represents the set of advertised certificate compression
|
|
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
|
|
// server certificate. All other forms of certificate compression are unsupported.
|
|
certCompressionAlgs []CertCompressionAlgo
|
|
}
|
|
|
|
// UClient returns a new uTLS client, with behavior depending on clientHelloID.
|
|
// Config CAN be nil, but make sure to eventually specify ServerName.
|
|
func UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID) *UConn {
|
|
if config == nil {
|
|
config = &Config{}
|
|
}
|
|
tlsConn := Conn{conn: conn, config: config, isClient: true}
|
|
handshakeState := PubClientHandshakeState{C: &tlsConn, Hello: &PubClientHelloMsg{}}
|
|
uconn := UConn{Conn: &tlsConn, ClientHelloID: clientHelloID, HandshakeState: handshakeState}
|
|
uconn.HandshakeState.uconn = &uconn
|
|
uconn.handshakeFn = uconn.clientHandshake
|
|
return &uconn
|
|
}
|
|
|
|
// BuildHandshakeState behavior varies based on ClientHelloID and
|
|
// whether it was already called before.
|
|
// If HelloGolang:
|
|
//
|
|
// [only once] make default ClientHello and overwrite existing state
|
|
//
|
|
// If any other mimicking ClientHelloID is used:
|
|
//
|
|
// [only once] make ClientHello based on ID and overwrite existing state
|
|
// [each call] apply uconn.Extensions config to internal crypto/tls structures
|
|
// [each call] marshal ClientHello.
|
|
//
|
|
// BuildHandshakeState is automatically called before uTLS performs handshake,
|
|
// amd should only be called explicitly to inspect/change fields of
|
|
// default/mimicked ClientHello.
|
|
func (uconn *UConn) BuildHandshakeState() error {
|
|
if uconn.ClientHelloID == HelloGolang {
|
|
if uconn.ClientHelloBuilt {
|
|
return nil
|
|
}
|
|
|
|
// use default Golang ClientHello.
|
|
hello, ecdheKey, err := uconn.makeClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uconn.HandshakeState.Hello = hello.getPublicPtr()
|
|
uconn.HandshakeState.State13.EcdheKey = ecdheKey
|
|
uconn.HandshakeState.C = uconn.Conn
|
|
} else {
|
|
if !uconn.ClientHelloBuilt {
|
|
err := uconn.applyPresetByID(uconn.ClientHelloID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if uconn.omitSNIExtension {
|
|
uconn.removeSNIExtension()
|
|
}
|
|
}
|
|
|
|
err := uconn.ApplyConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = uconn.MarshalClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
uconn.ClientHelloBuilt = true
|
|
return nil
|
|
}
|
|
|
|
// SetSessionState sets the session ticket, which may be preshared or fake.
|
|
// If session is nil, the body of session ticket extension will be unset,
|
|
// but the extension itself still MAY be present for mimicking purposes.
|
|
// Session tickets to be reused - use same cache on following connections.
|
|
func (uconn *UConn) SetSessionState(session *ClientSessionState) error {
|
|
var sessionTicket []uint8
|
|
if session != nil {
|
|
sessionTicket = session.ticket
|
|
uconn.HandshakeState.Session = session.session
|
|
}
|
|
uconn.HandshakeState.Hello.TicketSupported = true
|
|
uconn.HandshakeState.Hello.SessionTicket = sessionTicket
|
|
|
|
for _, ext := range uconn.Extensions {
|
|
st, ok := ext.(*SessionTicketExtension)
|
|
if !ok {
|
|
continue
|
|
}
|
|
st.Session = session
|
|
if session != nil {
|
|
if len(session.SessionTicket()) > 0 {
|
|
if uconn.GetSessionID != nil {
|
|
sid := uconn.GetSessionID(session.SessionTicket())
|
|
uconn.HandshakeState.Hello.SessionId = sid[:]
|
|
return nil
|
|
}
|
|
}
|
|
var sessionID [32]byte
|
|
_, err := io.ReadFull(uconn.config.rand(), sessionID[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
uconn.HandshakeState.Hello.SessionId = sessionID[:]
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If you want session tickets to be reused - use same cache on following connections
|
|
func (uconn *UConn) SetSessionCache(cache ClientSessionCache) {
|
|
uconn.config.ClientSessionCache = cache
|
|
uconn.HandshakeState.Hello.TicketSupported = true
|
|
}
|
|
|
|
// SetClientRandom sets client random explicitly.
|
|
// BuildHandshakeFirst() must be called before SetClientRandom.
|
|
// r must to be 32 bytes long.
|
|
func (uconn *UConn) SetClientRandom(r []byte) error {
|
|
if len(r) != 32 {
|
|
return errors.New("Incorrect client random length! Expected: 32, got: " + strconv.Itoa(len(r)))
|
|
} else {
|
|
uconn.HandshakeState.Hello.Random = make([]byte, 32)
|
|
copy(uconn.HandshakeState.Hello.Random, r)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (uconn *UConn) SetSNI(sni string) {
|
|
hname := hostnameInSNI(sni)
|
|
uconn.config.ServerName = hname
|
|
for _, ext := range uconn.Extensions {
|
|
sniExt, ok := ext.(*SNIExtension)
|
|
if ok {
|
|
sniExt.ServerName = hname
|
|
}
|
|
}
|
|
}
|
|
|
|
// RemoveSNIExtension removes SNI from the list of extensions sent in ClientHello
|
|
// It returns an error when used with HelloGolang ClientHelloID
|
|
func (uconn *UConn) RemoveSNIExtension() error {
|
|
if uconn.ClientHelloID == HelloGolang {
|
|
return fmt.Errorf("cannot call RemoveSNIExtension on a UConn with a HelloGolang ClientHelloID")
|
|
}
|
|
uconn.omitSNIExtension = true
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) removeSNIExtension() {
|
|
filteredExts := make([]TLSExtension, 0, len(uconn.Extensions))
|
|
for _, e := range uconn.Extensions {
|
|
if _, ok := e.(*SNIExtension); !ok {
|
|
filteredExts = append(filteredExts, e)
|
|
}
|
|
}
|
|
uconn.Extensions = filteredExts
|
|
}
|
|
|
|
// Handshake runs the client handshake using given clientHandshakeState
|
|
// Requires hs.hello, and, optionally, hs.session to be set.
|
|
func (c *UConn) Handshake() error {
|
|
return c.HandshakeContext(context.Background())
|
|
}
|
|
|
|
// HandshakeContext runs the client or server handshake
|
|
// protocol if it has not yet been run.
|
|
//
|
|
// The provided Context must be non-nil. If the context is canceled before
|
|
// the handshake is complete, the handshake is interrupted and an error is returned.
|
|
// Once the handshake has completed, cancellation of the context will not affect the
|
|
// connection.
|
|
func (c *UConn) HandshakeContext(ctx context.Context) error {
|
|
// Delegate to unexported method for named return
|
|
// without confusing documented signature.
|
|
return c.handshakeContext(ctx)
|
|
}
|
|
|
|
func (c *UConn) handshakeContext(ctx context.Context) (ret error) {
|
|
// Fast sync/atomic-based exit if there is no handshake in flight and the
|
|
// last one succeeded without an error. Avoids the expensive context setup
|
|
// and mutex for most Read and Write calls.
|
|
if c.isHandshakeComplete.Load() {
|
|
return nil
|
|
}
|
|
|
|
handshakeCtx, cancel := context.WithCancel(ctx)
|
|
// Note: defer this before starting the "interrupter" goroutine
|
|
// so that we can tell the difference between the input being canceled and
|
|
// this cancellation. In the former case, we need to close the connection.
|
|
defer cancel()
|
|
|
|
// Start the "interrupter" goroutine, if this context might be canceled.
|
|
// (The background context cannot).
|
|
//
|
|
// The interrupter goroutine waits for the input context to be done and
|
|
// closes the connection if this happens before the function returns.
|
|
if c.quic != nil {
|
|
c.quic.cancelc = handshakeCtx.Done()
|
|
c.quic.cancel = cancel
|
|
} else if ctx.Done() != nil {
|
|
done := make(chan struct{})
|
|
interruptRes := make(chan error, 1)
|
|
defer func() {
|
|
close(done)
|
|
if ctxErr := <-interruptRes; ctxErr != nil {
|
|
// Return context error to user.
|
|
ret = ctxErr
|
|
}
|
|
}()
|
|
go func() {
|
|
select {
|
|
case <-handshakeCtx.Done():
|
|
// Close the connection, discarding the error
|
|
_ = c.conn.Close()
|
|
interruptRes <- handshakeCtx.Err()
|
|
case <-done:
|
|
interruptRes <- nil
|
|
}
|
|
}()
|
|
}
|
|
|
|
c.handshakeMutex.Lock()
|
|
defer c.handshakeMutex.Unlock()
|
|
|
|
if err := c.handshakeErr; err != nil {
|
|
return err
|
|
}
|
|
if c.isHandshakeComplete.Load() {
|
|
return nil
|
|
}
|
|
|
|
c.in.Lock()
|
|
defer c.in.Unlock()
|
|
|
|
// [uTLS section begins]
|
|
if c.isClient {
|
|
err := c.BuildHandshakeState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// [uTLS section ends]
|
|
c.handshakeErr = c.handshakeFn(handshakeCtx)
|
|
if c.handshakeErr == nil {
|
|
c.handshakes++
|
|
} else {
|
|
// If an error occurred during the hadshake try to flush the
|
|
// alert that might be left in the buffer.
|
|
c.flush()
|
|
}
|
|
|
|
if c.handshakeErr == nil && !c.isHandshakeComplete.Load() {
|
|
c.handshakeErr = errors.New("tls: internal error: handshake should have had a result")
|
|
}
|
|
if c.handshakeErr != nil && c.isHandshakeComplete.Load() {
|
|
panic("tls: internal error: handshake returned an error but is marked successful")
|
|
}
|
|
|
|
if c.quic != nil {
|
|
if c.handshakeErr == nil {
|
|
c.quicHandshakeComplete()
|
|
// Provide the 1-RTT read secret now that the handshake is complete.
|
|
// The QUIC layer MUST NOT decrypt 1-RTT packets prior to completing
|
|
// the handshake (RFC 9001, Section 5.7).
|
|
c.quicSetReadSecret(QUICEncryptionLevelApplication, c.cipherSuite, c.in.trafficSecret)
|
|
} else {
|
|
var a alert
|
|
c.out.Lock()
|
|
if !errors.As(c.out.err, &a) {
|
|
a = alertInternalError
|
|
}
|
|
c.out.Unlock()
|
|
// Return an error which wraps both the handshake error and
|
|
// any alert error we may have sent, or alertInternalError
|
|
// if we didn't send an alert.
|
|
// Truncate the text of the alert to 0 characters.
|
|
c.handshakeErr = fmt.Errorf("%w%.0w", c.handshakeErr, AlertError(a))
|
|
}
|
|
close(c.quic.blockedc)
|
|
close(c.quic.signalc)
|
|
}
|
|
|
|
return c.handshakeErr
|
|
}
|
|
|
|
// Copy-pasted from tls.Conn in its entirety. But c.Handshake() is now utls' one, not tls.
|
|
// Write writes data to the connection.
|
|
func (c *UConn) Write(b []byte) (int, error) {
|
|
// interlock with Close below
|
|
for {
|
|
x := c.activeCall.Load()
|
|
if x&1 != 0 {
|
|
return 0, net.ErrClosed
|
|
}
|
|
if c.activeCall.CompareAndSwap(x, x+2) {
|
|
defer c.activeCall.Add(-2)
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := c.Handshake(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
c.out.Lock()
|
|
defer c.out.Unlock()
|
|
|
|
if err := c.out.err; err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if !c.isHandshakeComplete.Load() {
|
|
return 0, alertInternalError
|
|
}
|
|
|
|
if c.closeNotifySent {
|
|
return 0, errShutdown
|
|
}
|
|
|
|
// SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext
|
|
// attack when using block mode ciphers due to predictable IVs.
|
|
// This can be prevented by splitting each Application Data
|
|
// record into two records, effectively randomizing the IV.
|
|
//
|
|
// https://www.openssl.org/~bodo/tls-cbc.txt
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=665814
|
|
// https://www.imperialviolet.org/2012/01/15/beastfollowup.html
|
|
|
|
var m int
|
|
if len(b) > 1 && c.vers <= VersionTLS10 {
|
|
if _, ok := c.out.cipher.(cipher.BlockMode); ok {
|
|
n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1])
|
|
if err != nil {
|
|
return n, c.out.setErrorLocked(err)
|
|
}
|
|
m, b = 1, b[1:]
|
|
}
|
|
}
|
|
|
|
n, err := c.writeRecordLocked(recordTypeApplicationData, b)
|
|
return n + m, c.out.setErrorLocked(err)
|
|
}
|
|
|
|
// clientHandshakeWithOneState checks that exactly one expected state is set (1.2 or 1.3)
|
|
// and performs client TLS handshake with that state
|
|
func (c *UConn) clientHandshake(ctx context.Context) (err error) {
|
|
// [uTLS section begins]
|
|
hello := c.HandshakeState.Hello.getPrivatePtr()
|
|
defer func() { c.HandshakeState.Hello = hello.getPublicPtr() }()
|
|
|
|
sessionIsAlreadySet := c.HandshakeState.Session != nil
|
|
|
|
// after this point exactly 1 out of 2 HandshakeState pointers is non-nil,
|
|
// useTLS13 variable tells which pointer
|
|
// [uTLS section ends]
|
|
|
|
if c.config == nil {
|
|
c.config = defaultConfig()
|
|
}
|
|
|
|
// This may be a renegotiation handshake, in which case some fields
|
|
// need to be reset.
|
|
c.didResume = false
|
|
|
|
// [uTLS section begins]
|
|
// don't make new ClientHello, use hs.hello
|
|
// preserve the checks from beginning and end of makeClientHello()
|
|
if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify && len(c.config.InsecureServerNameToVerify) == 0 {
|
|
return errors.New("tls: at least one of ServerName, InsecureSkipVerify or InsecureServerNameToVerify must be specified in the tls.Config")
|
|
}
|
|
|
|
nextProtosLength := 0
|
|
for _, proto := range c.config.NextProtos {
|
|
if l := len(proto); l == 0 || l > 255 {
|
|
return errors.New("tls: invalid NextProtos value")
|
|
} else {
|
|
nextProtosLength += 1 + l
|
|
}
|
|
}
|
|
|
|
if nextProtosLength > 0xffff {
|
|
return errors.New("tls: NextProtos values too large")
|
|
}
|
|
|
|
if c.handshakes > 0 {
|
|
hello.secureRenegotiation = c.clientFinished[:]
|
|
}
|
|
// [uTLS section ends]
|
|
|
|
session, earlySecret, binderKey, err := c.loadSession(hello)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if session != nil {
|
|
defer func() {
|
|
// If we got a handshake failure when resuming a session, throw away
|
|
// the session ticket. See RFC 5077, Section 3.2.
|
|
//
|
|
// RFC 8446 makes no mention of dropping tickets on failure, but it
|
|
// does require servers to abort on invalid binders, so we need to
|
|
// delete tickets to recover from a corrupted PSK.
|
|
if err != nil {
|
|
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
|
c.config.ClientSessionCache.Put(cacheKey, nil)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
cacheKey := c.clientSessionCacheKey()
|
|
if c.config.ClientSessionCache != nil {
|
|
cs, ok := c.config.ClientSessionCache.Get(cacheKey)
|
|
if !sessionIsAlreadySet && ok { // uTLS: do not overwrite already set session
|
|
err = c.SetSessionState(cs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, err := c.writeHandshakeRecord(hello, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
msg, err := c.readHandshake(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
serverHello, ok := msg.(*serverHelloMsg)
|
|
if !ok {
|
|
c.sendAlert(alertUnexpectedMessage)
|
|
return unexpectedMessageError(serverHello, msg)
|
|
}
|
|
|
|
if err := c.pickTLSVersion(serverHello); err != nil {
|
|
return err
|
|
}
|
|
|
|
// uTLS: do not create new handshakeState, use existing one
|
|
if c.vers == VersionTLS13 {
|
|
hs13 := c.HandshakeState.toPrivate13()
|
|
hs13.serverHello = serverHello
|
|
hs13.hello = hello
|
|
if !sessionIsAlreadySet {
|
|
hs13.earlySecret = earlySecret
|
|
hs13.binderKey = binderKey
|
|
}
|
|
hs13.ctx = ctx
|
|
// In TLS 1.3, session tickets are delivered after the handshake.
|
|
err = hs13.handshake()
|
|
if handshakeState := hs13.toPublic13(); handshakeState != nil {
|
|
c.HandshakeState = *handshakeState
|
|
}
|
|
return err
|
|
}
|
|
|
|
hs12 := c.HandshakeState.toPrivate12()
|
|
hs12.serverHello = serverHello
|
|
hs12.hello = hello
|
|
hs12.ctx = ctx
|
|
err = hs12.handshake()
|
|
if handshakeState := hs12.toPublic12(); handshakeState != nil {
|
|
c.HandshakeState = *handshakeState
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If we had a successful handshake and hs.session is different from
|
|
// the one already cached - cache a new one.
|
|
if cacheKey != "" && hs12.session != nil && session != hs12.session {
|
|
hs12cs := &ClientSessionState{
|
|
ticket: hs12.ticket,
|
|
session: hs12.session,
|
|
}
|
|
|
|
c.config.ClientSessionCache.Put(cacheKey, hs12cs)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) ApplyConfig() error {
|
|
for _, ext := range uconn.Extensions {
|
|
err := ext.writeToUConn(uconn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) MarshalClientHello() error {
|
|
hello := uconn.HandshakeState.Hello
|
|
headerLength := 2 + 32 + 1 + len(hello.SessionId) +
|
|
2 + len(hello.CipherSuites)*2 +
|
|
1 + len(hello.CompressionMethods)
|
|
|
|
extensionsLen := 0
|
|
var paddingExt *UtlsPaddingExtension
|
|
for _, ext := range uconn.Extensions {
|
|
if pe, ok := ext.(*UtlsPaddingExtension); !ok {
|
|
// If not padding - just add length of extension to total length
|
|
extensionsLen += ext.Len()
|
|
} else {
|
|
// If padding - process it later
|
|
if paddingExt == nil {
|
|
paddingExt = pe
|
|
} else {
|
|
return errors.New("multiple padding extensions!")
|
|
}
|
|
}
|
|
}
|
|
|
|
if paddingExt != nil {
|
|
// determine padding extension presence and length
|
|
paddingExt.Update(headerLength + 4 + extensionsLen + 2)
|
|
extensionsLen += paddingExt.Len()
|
|
}
|
|
|
|
helloLen := headerLength
|
|
if len(uconn.Extensions) > 0 {
|
|
helloLen += 2 + extensionsLen // 2 bytes for extensions' length
|
|
}
|
|
|
|
helloBuffer := bytes.Buffer{}
|
|
bufferedWriter := bufio.NewWriterSize(&helloBuffer, helloLen+4) // 1 byte for tls record type, 3 for length
|
|
// We use buffered Writer to avoid checking write errors after every Write(): whenever first error happens
|
|
// Write() will become noop, and error will be accessible via Flush(), which is called once in the end
|
|
|
|
binary.Write(bufferedWriter, binary.BigEndian, typeClientHello)
|
|
helloLenBytes := []byte{byte(helloLen >> 16), byte(helloLen >> 8), byte(helloLen)} // poor man's uint24
|
|
binary.Write(bufferedWriter, binary.BigEndian, helloLenBytes)
|
|
binary.Write(bufferedWriter, binary.BigEndian, hello.Vers)
|
|
|
|
binary.Write(bufferedWriter, binary.BigEndian, hello.Random)
|
|
|
|
binary.Write(bufferedWriter, binary.BigEndian, uint8(len(hello.SessionId)))
|
|
binary.Write(bufferedWriter, binary.BigEndian, hello.SessionId)
|
|
|
|
binary.Write(bufferedWriter, binary.BigEndian, uint16(len(hello.CipherSuites)<<1))
|
|
for _, suite := range hello.CipherSuites {
|
|
binary.Write(bufferedWriter, binary.BigEndian, suite)
|
|
}
|
|
|
|
binary.Write(bufferedWriter, binary.BigEndian, uint8(len(hello.CompressionMethods)))
|
|
binary.Write(bufferedWriter, binary.BigEndian, hello.CompressionMethods)
|
|
|
|
if len(uconn.Extensions) > 0 {
|
|
binary.Write(bufferedWriter, binary.BigEndian, uint16(extensionsLen))
|
|
for _, ext := range uconn.Extensions {
|
|
bufferedWriter.ReadFrom(ext)
|
|
}
|
|
}
|
|
|
|
err := bufferedWriter.Flush()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if helloBuffer.Len() != 4+helloLen {
|
|
return errors.New("utls: unexpected ClientHello length. Expected: " + strconv.Itoa(4+helloLen) +
|
|
". Got: " + strconv.Itoa(helloBuffer.Len()))
|
|
}
|
|
|
|
hello.Raw = helloBuffer.Bytes()
|
|
return nil
|
|
}
|
|
|
|
// get current state of cipher and encrypt zeros to get keystream
|
|
func (uconn *UConn) GetOutKeystream(length int) ([]byte, error) {
|
|
zeros := make([]byte, length)
|
|
|
|
if outCipher, ok := uconn.out.cipher.(cipher.AEAD); ok {
|
|
// AEAD.Seal() does not mutate internal state, other ciphers might
|
|
return outCipher.Seal(nil, uconn.out.seq[:], zeros, nil), nil
|
|
}
|
|
return nil, errors.New("could not convert OutCipher to cipher.AEAD")
|
|
}
|
|
|
|
// SetTLSVers sets min and max TLS version in all appropriate places.
|
|
// Function will use first non-zero version parsed in following order:
|
|
// 1. Provided minTLSVers, maxTLSVers
|
|
// 2. specExtensions may have SupportedVersionsExtension
|
|
// 3. [default] min = TLS 1.0, max = TLS 1.2
|
|
//
|
|
// Error is only returned if things are in clearly undesirable state
|
|
// to help user fix them.
|
|
func (uconn *UConn) SetTLSVers(minTLSVers, maxTLSVers uint16, specExtensions []TLSExtension) error {
|
|
if minTLSVers == 0 && maxTLSVers == 0 {
|
|
// if version is not set explicitly in the ClientHelloSpec, check the SupportedVersions extension
|
|
supportedVersionsExtensionsPresent := 0
|
|
for _, e := range specExtensions {
|
|
switch ext := e.(type) {
|
|
case *SupportedVersionsExtension:
|
|
findVersionsInSupportedVersionsExtensions := func(versions []uint16) (uint16, uint16) {
|
|
// returns (minVers, maxVers)
|
|
minVers := uint16(0)
|
|
maxVers := uint16(0)
|
|
for _, vers := range versions {
|
|
if isGREASEUint16(vers) {
|
|
continue
|
|
}
|
|
if maxVers < vers || maxVers == 0 {
|
|
maxVers = vers
|
|
}
|
|
if minVers > vers || minVers == 0 {
|
|
minVers = vers
|
|
}
|
|
}
|
|
return minVers, maxVers
|
|
}
|
|
|
|
supportedVersionsExtensionsPresent += 1
|
|
minTLSVers, maxTLSVers = findVersionsInSupportedVersionsExtensions(ext.Versions)
|
|
if minTLSVers == 0 && maxTLSVers == 0 {
|
|
return fmt.Errorf("SupportedVersions extension has invalid Versions field")
|
|
} // else: proceed
|
|
}
|
|
}
|
|
switch supportedVersionsExtensionsPresent {
|
|
case 0:
|
|
// if mandatory for TLS 1.3 extension is not present, just default to 1.2
|
|
minTLSVers = VersionTLS10
|
|
maxTLSVers = VersionTLS12
|
|
case 1:
|
|
default:
|
|
return fmt.Errorf("uconn.Extensions contains %v separate SupportedVersions extensions",
|
|
supportedVersionsExtensionsPresent)
|
|
}
|
|
}
|
|
|
|
if minTLSVers < VersionTLS10 || minTLSVers > VersionTLS13 {
|
|
return fmt.Errorf("uTLS does not support 0x%X as min version", minTLSVers)
|
|
}
|
|
|
|
if maxTLSVers < VersionTLS10 || maxTLSVers > VersionTLS13 {
|
|
return fmt.Errorf("uTLS does not support 0x%X as max version", maxTLSVers)
|
|
}
|
|
|
|
uconn.HandshakeState.Hello.SupportedVersions = makeSupportedVersions(minTLSVers, maxTLSVers)
|
|
uconn.config.MinVersion = minTLSVers
|
|
uconn.config.MaxVersion = maxTLSVers
|
|
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) SetUnderlyingConn(c net.Conn) {
|
|
uconn.Conn.conn = c
|
|
}
|
|
|
|
func (uconn *UConn) GetUnderlyingConn() net.Conn {
|
|
return uconn.Conn.conn
|
|
}
|
|
|
|
// MakeConnWithCompleteHandshake allows to forge both server and client side TLS connections.
|
|
// Major Hack Alert.
|
|
func MakeConnWithCompleteHandshake(tcpConn net.Conn, version uint16, cipherSuite uint16, masterSecret []byte, clientRandom []byte, serverRandom []byte, isClient bool) *Conn {
|
|
tlsConn := &Conn{conn: tcpConn, config: &Config{}, isClient: isClient}
|
|
cs := cipherSuiteByID(cipherSuite)
|
|
if cs != nil {
|
|
// This is mostly borrowed from establishKeys()
|
|
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
|
|
keysFromMasterSecret(version, cs, masterSecret, clientRandom, serverRandom,
|
|
cs.macLen, cs.keyLen, cs.ivLen)
|
|
|
|
var clientCipher, serverCipher interface{}
|
|
var clientHash, serverHash hash.Hash
|
|
if cs.cipher != nil {
|
|
clientCipher = cs.cipher(clientKey, clientIV, true /* for reading */)
|
|
clientHash = cs.mac(clientMAC)
|
|
serverCipher = cs.cipher(serverKey, serverIV, false /* not for reading */)
|
|
serverHash = cs.mac(serverMAC)
|
|
} else {
|
|
clientCipher = cs.aead(clientKey, clientIV)
|
|
serverCipher = cs.aead(serverKey, serverIV)
|
|
}
|
|
|
|
if isClient {
|
|
tlsConn.in.prepareCipherSpec(version, serverCipher, serverHash)
|
|
tlsConn.out.prepareCipherSpec(version, clientCipher, clientHash)
|
|
} else {
|
|
tlsConn.in.prepareCipherSpec(version, clientCipher, clientHash)
|
|
tlsConn.out.prepareCipherSpec(version, serverCipher, serverHash)
|
|
}
|
|
|
|
// skip the handshake states
|
|
tlsConn.isHandshakeComplete.Store(true)
|
|
tlsConn.cipherSuite = cipherSuite
|
|
tlsConn.haveVers = true
|
|
tlsConn.vers = version
|
|
|
|
// Update to the new cipher specs
|
|
// and consume the finished messages
|
|
tlsConn.in.changeCipherSpec()
|
|
tlsConn.out.changeCipherSpec()
|
|
|
|
tlsConn.in.incSeq()
|
|
tlsConn.out.incSeq()
|
|
|
|
return tlsConn
|
|
} else {
|
|
// TODO: Support TLS 1.3 Cipher Suites
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func makeSupportedVersions(minVers, maxVers uint16) []uint16 {
|
|
a := make([]uint16, maxVers-minVers+1)
|
|
for i := range a {
|
|
a[i] = maxVers - uint16(i)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Extending (*Conn).readHandshake() to support more customized handshake messages.
|
|
func (c *Conn) utlsHandshakeMessageType(msgType byte) (handshakeMessage, error) {
|
|
switch msgType {
|
|
case utlsTypeCompressedCertificate:
|
|
return new(utlsCompressedCertificateMsg), nil
|
|
case utlsTypeEncryptedExtensions:
|
|
if c.isClient {
|
|
return new(encryptedExtensionsMsg), nil
|
|
} else {
|
|
return new(utlsClientEncryptedExtensionsMsg), nil
|
|
}
|
|
default:
|
|
return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
|
}
|
|
}
|
|
|
|
// Extending (*Conn).connectionStateLocked()
|
|
func (c *Conn) utlsConnectionStateLocked(state *ConnectionState) {
|
|
state.PeerApplicationSettings = c.utls.peerApplicationSettings
|
|
}
|
|
|
|
type utlsConnExtraFields struct {
|
|
hasApplicationSettings bool
|
|
peerApplicationSettings []byte
|
|
localApplicationSettings []byte
|
|
}
|