mirror of
https://github.com/refraction-networking/utls.git
synced 2025-03-31 10:37:36 +03:00
891 lines
28 KiB
Go
891 lines
28 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"
|
|
"net"
|
|
"strconv"
|
|
)
|
|
|
|
type ClientHelloBuildStatus int
|
|
|
|
const NotBuilt ClientHelloBuildStatus = 0
|
|
const BuildByUtls ClientHelloBuildStatus = 1
|
|
const BuildByGoTLS ClientHelloBuildStatus = 2
|
|
|
|
type UConn struct {
|
|
*Conn
|
|
|
|
Extensions []TLSExtension
|
|
ClientHelloID ClientHelloID
|
|
sessionController *sessionController
|
|
|
|
clientHelloBuildStatus ClientHelloBuildStatus
|
|
clientHelloSpec *ClientHelloSpec
|
|
|
|
HandshakeState PubClientHandshakeState
|
|
|
|
greaseSeed [ssl_grease_last_index]uint16
|
|
|
|
omitSNIExtension bool
|
|
|
|
// skipResumptionOnNilExtension is copied from `Config.PreferSkipResumptionOnNilExtension`.
|
|
//
|
|
// By default, if ClientHelloSpec is predefined or utls-generated (as opposed to HelloCustom), this flag will be updated to true.
|
|
skipResumptionOnNilExtension 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
|
|
|
|
// ech extension is a shortcut to the ECH extension in the Extensions slice if there is one.
|
|
ech ECHExtension
|
|
|
|
// echCtx is the echContex returned by makeClientHello()
|
|
echCtx *echClientContext
|
|
}
|
|
|
|
// 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
|
|
uconn.sessionController = newSessionController(&uconn)
|
|
uconn.utls.sessionController = uconn.sessionController
|
|
uconn.skipResumptionOnNilExtension = config.PreferSkipResumptionOnNilExtension || clientHelloID.Client != helloCustom
|
|
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,
|
|
// and should only be called explicitly to inspect/change fields of
|
|
// default/mimicked ClientHello.
|
|
// With the excpetion of session ticket and psk extensions, which cannot be changed
|
|
// after calling BuildHandshakeState, all other fields can be modified.
|
|
func (uconn *UConn) BuildHandshakeState() error {
|
|
return uconn.buildHandshakeState(true)
|
|
}
|
|
|
|
// BuildHandshakeStateWithoutSession is the same as BuildHandshakeState, but does not
|
|
// set the session. This is only useful when you want to inspect the ClientHello before
|
|
// setting the session manually through SetSessionTicketExtension or SetPSKExtension.
|
|
// BuildHandshakeState is automatically called before uTLS performs handshake.
|
|
func (uconn *UConn) BuildHandshakeStateWithoutSession() error {
|
|
return uconn.buildHandshakeState(false)
|
|
}
|
|
|
|
func (uconn *UConn) buildHandshakeState(loadSession bool) error {
|
|
if uconn.ClientHelloID == HelloGolang {
|
|
if uconn.clientHelloBuildStatus == BuildByGoTLS {
|
|
return nil
|
|
}
|
|
uAssert(uconn.clientHelloBuildStatus == NotBuilt, "BuildHandshakeState failed: invalid call, client hello has already been built by utls")
|
|
|
|
// use default Golang ClientHello.
|
|
hello, keySharePrivate, ech, err := uconn.makeClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uconn.HandshakeState.Hello = hello.getPublicPtr()
|
|
uconn.HandshakeState.State13.KeyShareKeys = keySharePrivate.ToPublic()
|
|
uconn.HandshakeState.C = uconn.Conn
|
|
uconn.echCtx = ech
|
|
uconn.clientHelloBuildStatus = BuildByGoTLS
|
|
} else {
|
|
uAssert(uconn.clientHelloBuildStatus == BuildByUtls || uconn.clientHelloBuildStatus == NotBuilt, "BuildHandshakeState failed: invalid call, client hello has already been built by go-tls")
|
|
if uconn.clientHelloBuildStatus == NotBuilt {
|
|
err := uconn.applyPresetByID(uconn.ClientHelloID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if uconn.omitSNIExtension {
|
|
uconn.removeSNIExtension()
|
|
}
|
|
}
|
|
|
|
err := uconn.ApplyConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if loadSession {
|
|
err = uconn.uLoadSession()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = uconn.MarshalClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if loadSession {
|
|
uconn.uApplyPatch()
|
|
uconn.sessionController.finalCheck()
|
|
uconn.clientHelloBuildStatus = BuildByUtls
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) uLoadSession() error {
|
|
if cfg := uconn.config; cfg.SessionTicketsDisabled || cfg.ClientSessionCache == nil {
|
|
return nil
|
|
}
|
|
switch uconn.sessionController.shouldLoadSession() {
|
|
case shouldReturn:
|
|
case shouldSetTicket:
|
|
uconn.sessionController.setSessionTicketToUConn()
|
|
case shouldSetPsk:
|
|
uconn.sessionController.setPskToUConn()
|
|
case shouldLoad:
|
|
hello := uconn.HandshakeState.Hello.getPrivatePtr()
|
|
uconn.sessionController.utlsAboutToLoadSession()
|
|
session, earlySecret, binderKey, err := uconn.loadSession(hello)
|
|
if session == nil || err != nil {
|
|
return err
|
|
}
|
|
if session.version == VersionTLS12 {
|
|
// We use the session ticket extension for tls 1.2 session resumption
|
|
uconn.sessionController.initSessionTicketExt(session, hello.sessionTicket)
|
|
uconn.sessionController.setSessionTicketToUConn()
|
|
} else {
|
|
uconn.sessionController.initPskExt(session, earlySecret, binderKey, hello.pskIdentities)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (uconn *UConn) uApplyPatch() {
|
|
helloLen := len(uconn.HandshakeState.Hello.Raw)
|
|
if uconn.sessionController.shouldUpdateBinders() {
|
|
uconn.sessionController.updateBinders()
|
|
uconn.sessionController.setPskToUConn()
|
|
}
|
|
uAssert(helloLen == len(uconn.HandshakeState.Hello.Raw), "tls: uApplyPatch Failed: the patch should never change the length of the marshaled clientHello")
|
|
}
|
|
|
|
func (uconn *UConn) DidTls12Resume() bool {
|
|
return uconn.didResume
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// Deprecated: This method is deprecated in favor of SetSessionTicketExtension,
|
|
// as it only handles session override of TLS 1.2
|
|
func (uconn *UConn) SetSessionState(session *ClientSessionState) error {
|
|
sessionTicketExt := &SessionTicketExtension{Initialized: true}
|
|
if session != nil {
|
|
sessionTicketExt.Ticket = session.session.ticket
|
|
sessionTicketExt.Session = session.session
|
|
}
|
|
return uconn.SetSessionTicketExtension(sessionTicketExt)
|
|
}
|
|
|
|
// SetSessionTicket sets the session ticket extension.
|
|
// If extension is nil, this will be a no-op.
|
|
func (uconn *UConn) SetSessionTicketExtension(sessionTicketExt ISessionTicketExtension) error {
|
|
if uconn.config.SessionTicketsDisabled || uconn.config.ClientSessionCache == nil {
|
|
return fmt.Errorf("tls: SetSessionTicketExtension failed: session is disabled")
|
|
}
|
|
if sessionTicketExt == nil {
|
|
return nil
|
|
}
|
|
return uconn.sessionController.overrideSessionTicketExt(sessionTicketExt)
|
|
}
|
|
|
|
// SetPskExtension sets the psk extension for tls 1.3 resumption. This is a no-op if the psk is nil.
|
|
func (uconn *UConn) SetPskExtension(pskExt PreSharedKeyExtension) error {
|
|
if uconn.config.SessionTicketsDisabled || uconn.config.ClientSessionCache == nil {
|
|
return fmt.Errorf("tls: SetPskExtension failed: session is disabled")
|
|
}
|
|
if pskExt == nil {
|
|
return nil
|
|
}
|
|
|
|
uconn.HandshakeState.Hello.TicketSupported = true
|
|
return uconn.sessionController.overridePskExt(pskExt)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
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 {
|
|
if len(uconn.config.ECHConfigs) > 0 && uconn.ech != nil {
|
|
if err := uconn.ech.Configure(uconn.config.ECHConfigs); err != nil {
|
|
return err
|
|
}
|
|
return uconn.ech.MarshalClientHello(uconn)
|
|
}
|
|
|
|
return uconn.MarshalClientHelloNoECH() // if no ECH pointer, just marshal normally
|
|
}
|
|
|
|
// MarshalClientHelloNoECH marshals ClientHello as if there was no
|
|
// ECH extension present.
|
|
func (uconn *UConn) MarshalClientHelloNoECH() 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 // reference to padding extension, if present
|
|
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 {
|
|
if _, err := bufferedWriter.ReadFrom(ext); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
state.ECHRetryConfigs = c.utls.echRetryConfigs
|
|
}
|
|
|
|
type utlsConnExtraFields struct {
|
|
// Application Settings (ALPS)
|
|
hasApplicationSettings bool
|
|
peerApplicationSettings []byte
|
|
localApplicationSettings []byte
|
|
|
|
// Encrypted Client Hello (ECH)
|
|
echRetryConfigs []ECHConfig
|
|
|
|
sessionController *sessionController
|
|
}
|
|
|
|
// Read reads data from the connection.
|
|
//
|
|
// As Read calls [Conn.Handshake], in order to prevent indefinite blocking a deadline
|
|
// must be set for both Read and [Conn.Write] before Read is called when the handshake
|
|
// has not yet completed. See [Conn.SetDeadline], [Conn.SetReadDeadline], and
|
|
// [Conn.SetWriteDeadline].
|
|
func (c *UConn) Read(b []byte) (int, error) {
|
|
if err := c.Handshake(); err != nil {
|
|
return 0, err
|
|
}
|
|
if len(b) == 0 {
|
|
// Put this after Handshake, in case people were calling
|
|
// Read(nil) for the side effect of the Handshake.
|
|
return 0, nil
|
|
}
|
|
|
|
c.in.Lock()
|
|
defer c.in.Unlock()
|
|
|
|
for c.input.Len() == 0 {
|
|
if err := c.readRecord(); err != nil {
|
|
return 0, err
|
|
}
|
|
for c.hand.Len() > 0 {
|
|
if err := c.handlePostHandshakeMessage(); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
}
|
|
|
|
n, _ := c.input.Read(b)
|
|
|
|
// If a close-notify alert is waiting, read it so that we can return (n,
|
|
// EOF) instead of (n, nil), to signal to the HTTP response reading
|
|
// goroutine that the connection is now closed. This eliminates a race
|
|
// where the HTTP response reading goroutine would otherwise not observe
|
|
// the EOF until its next read, by which time a client goroutine might
|
|
// have already tried to reuse the HTTP connection for a new request.
|
|
// See https://golang.org/cl/76400046 and https://golang.org/issue/3514
|
|
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
|
recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
|
if err := c.readRecord(); err != nil {
|
|
return n, err // will be io.EOF on closeNotify
|
|
}
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
// handleRenegotiation processes a HelloRequest handshake message.
|
|
func (c *UConn) handleRenegotiation() error {
|
|
if c.vers == VersionTLS13 {
|
|
return errors.New("tls: internal error: unexpected renegotiation")
|
|
}
|
|
|
|
msg, err := c.readHandshake(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
helloReq, ok := msg.(*helloRequestMsg)
|
|
if !ok {
|
|
c.sendAlert(alertUnexpectedMessage)
|
|
return unexpectedMessageError(helloReq, msg)
|
|
}
|
|
|
|
if !c.isClient {
|
|
return c.sendAlert(alertNoRenegotiation)
|
|
}
|
|
|
|
switch c.config.Renegotiation {
|
|
case RenegotiateNever:
|
|
return c.sendAlert(alertNoRenegotiation)
|
|
case RenegotiateOnceAsClient:
|
|
if c.handshakes > 1 {
|
|
return c.sendAlert(alertNoRenegotiation)
|
|
}
|
|
case RenegotiateFreelyAsClient:
|
|
// Ok.
|
|
default:
|
|
c.sendAlert(alertInternalError)
|
|
return errors.New("tls: unknown Renegotiation value")
|
|
}
|
|
|
|
c.handshakeMutex.Lock()
|
|
defer c.handshakeMutex.Unlock()
|
|
|
|
c.isHandshakeComplete.Store(false)
|
|
|
|
// [uTLS section begins]
|
|
if err = c.BuildHandshakeState(); err != nil {
|
|
return err
|
|
}
|
|
// [uTLS section ends]
|
|
if c.handshakeErr = c.clientHandshake(context.Background()); c.handshakeErr == nil {
|
|
c.handshakes++
|
|
}
|
|
return c.handshakeErr
|
|
}
|
|
|
|
// handlePostHandshakeMessage processes a handshake message arrived after the
|
|
// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation.
|
|
func (c *UConn) handlePostHandshakeMessage() error {
|
|
if c.vers != VersionTLS13 {
|
|
return c.handleRenegotiation()
|
|
}
|
|
|
|
msg, err := c.readHandshake(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.retryCount++
|
|
if c.retryCount > maxUselessRecords {
|
|
c.sendAlert(alertUnexpectedMessage)
|
|
return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case *newSessionTicketMsgTLS13:
|
|
return c.handleNewSessionTicket(msg)
|
|
case *keyUpdateMsg:
|
|
return c.handleKeyUpdate(msg)
|
|
}
|
|
// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest
|
|
// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an
|
|
// unexpected_message alert here doesn't provide it with enough information to distinguish
|
|
// this condition from other unexpected messages. This is probably fine.
|
|
c.sendAlert(alertUnexpectedMessage)
|
|
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
|
|
}
|