mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-01 19:17:36 +03:00
* uTLS: X25519Kyber768Draft00 hybrid post-quantum key agreement by cloudflare/go (#222) * crypto/tls: Add hybrid post-quantum key agreement (#13) * import: client-side KEM from cloudflare/go * import: server-side KEM from cloudflare/go * fix: modify test to get rid of CFEvents. Note: uTLS does not promise any server-side functionality, and this change is made to be able to conduct unit tests which requires both side to be able to handle KEM Curves. Co-authored-by: Christopher Wood <caw@heapingbits.net> Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> ---- Based on: * crypto/tls: Add hybrid post-quantum key agreement Adds X25519Kyber512Draft00, X25519Kyber768Draft00, and P256Kyber768Draft00 hybrid post-quantum key agreements with temporary group identifiers. The hybrid post-quantum key exchanges uses plain X{25519,448} instead of HPKE, which we assume will be more likely to be adopted. The order is chosen to match CECPQ2. Not enabled by default. Adds CFEvents to detect `HelloRetryRequest`s and to signal which key agreement was used. Co-authored-by: Christopher Wood <caw@heapingbits.net> [bas, 1.20.1: also adds P256Kyber768Draft00] [pwu, 1.20.4: updated circl to v1.3.3, moved code to cfevent.go] * crypto: add support for CIRCL signature schemes * only partially port the commit from cloudflare/go. We would stick to the official x509 at the cost of incompatibility. Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> Co-Authored-By: Christopher Patton <3453007+cjpatton@users.noreply.github.com> Co-Authored-By: Peter Wu <peter@lekensteyn.nl> * crypto/tls: add new X25519Kyber768Draft00 code point Ported from cloudflare/go to support the upcoming new post-quantum keyshare. ---- * Point tls.X25519Kyber768Draft00 to the new 0x6399 identifier while the old 0xfe31 identifier is available as tls.X25519Kyber768Draft00Old. * Make sure that the kem.PrivateKey can always be mapped to the CurveID that was linked to it. This is needed since we now have two ID aliasing to the same scheme, and clients need to be able to detect whether the key share presented by the server actually matches the key share that the client originally sent. * Update tests, add the new identifier and remove unnecessary code. Link: https://mailarchive.ietf.org/arch/msg/tls/HAWpNpgptl--UZNSYuvsjB-Pc2k/ Link: https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/02/ Co-Authored-By: Peter Wu <peter@lekensteyn.nl> Co-Authored-By: Bas Westerbaan <bas@westerbaan.name> --------- Co-authored-by: Bas Westerbaan <bas@westerbaan.name> Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com> Co-authored-by: Peter Wu <peter@lekensteyn.nl> * new: enable PQ parrots (#225) * Redesign KeySharesEcdheParameters into KeySharesParameters which supports multiple types of keys. * Optimize program logic to prevent using unwanted keys * new: more parrots and safety update (#227) * new: PQ and other parrots Add new preset parrots: - HelloChrome_114_Padding_PSK_Shuf - HelloChrome_115_PQ - HelloChrome_115_PQ_PSK * new: ShuffleChromeTLSExtensions Implement a new function `ShuffleChromeTLSExtensions(exts []TLSExtension) []TLSExtension`. * update: include psk parameter for parrot-related functions Update following functions' prototype to accept an optional pskExtension (of type *FakePreSharedKeyExtension): - `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID)` => `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)` - `UTLSIdToSpec(id ClientHelloID)` => `UTLSIdToSpec(id ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)` * new: pre-defined error from UTLSIdToSpec Update UTLSIdToSpec to return more comprehensive errors by pre-defining them, allowing easier error comparing/unwrapping. * new: UtlsPreSharedKeyExtension In `u_pre_shared_key.go`, create `PreSharedKeyExtension` as an interface, with 3 implementations: - `UtlsPreSharedKeyExtension` implements full support for `pre_shared_key` less resuming after seeing HRR. - `FakePreSharedKeyExtension` uses CipherSuiteID, SessionSecret and Identities to calculate the corresponding binders and send them, without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails. - `HardcodedPreSharedKeyExtension` allows user to hardcode Identities and Binders to be sent in the extension without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails. TODO: Only one of FakePreSharedKeyExtension and HardcodedPreSharedKeyExtension should be kept, the other one should be just removed. We still need to learn more of the safety of hardcoding both Identities and Binders without recalculating the latter. * update: PSK minor changes and example * Updates PSK implementations for more comprehensible interfaces when applying preset/json/raw fingerprints. * Revert FakePreSharedKeyExtension to the old implementation. Add binder size checking. * Implement TLS-PSK example New bug: setting `tls.Config.ClientSessionCache` will cause PSK to fail. Currently users must set only `tls.UtlsPreSharedKeyExtension.ClientSessionCacheOverride`. * fix: PSK failing if config session cache set * Fix a bug causing PSK to fail if Config.ClientSessionCache is set. * Removed `ClientSessionCacheOverride` from `UtlsPreSharedKeyExtension`. Set the `ClientSessionCache` in `Config`! Co-Authored-By: zeeker999 <13848632+zeeker999@users.noreply.github.com> * Optimize tls resumption (#235) * feat: bug fix and refactor * feat: improve example docs: add detailed explanation about the design feat: add assertion on uApplyPatch * fix: address comments feat: add option `OmitEmptyPsk` and throw error on empty psk by default feat: revert changes to public interfaces * fix: weird residue caused by merging conflict * fix: remove merge conflict residue code --------- Co-authored-by: Bas Westerbaan <bas@westerbaan.name> Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com> Co-authored-by: Peter Wu <peter@lekensteyn.nl> Co-authored-by: zeeker999 <13848632+zeeker999@users.noreply.github.com> Co-authored-by: 3andne <52860475+3andne@users.noreply.github.com>
863 lines
26 KiB
Go
863 lines
26 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"
|
|
"crypto/ecdh"
|
|
"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
|
|
|
|
HandshakeState PubClientHandshakeState
|
|
|
|
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
|
|
uconn.sessionController = newSessionController(&uconn)
|
|
uconn.utls.sessionController = uconn.sessionController
|
|
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.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, err := uconn.makeClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uconn.HandshakeState.Hello = hello.getPublicPtr()
|
|
if ecdheKey, ok := keySharePrivate.(*ecdh.PrivateKey); ok {
|
|
uconn.HandshakeState.State13.EcdheKey = ecdheKey
|
|
} else if kemKey, ok := keySharePrivate.(*kemPrivateKey); ok {
|
|
uconn.HandshakeState.State13.KEMKey = kemKey.ToPublic()
|
|
} else {
|
|
return fmt.Errorf("uTLS: unknown keySharePrivate type: %T", keySharePrivate)
|
|
}
|
|
uconn.HandshakeState.C = uconn.Conn
|
|
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
|
|
}
|
|
|
|
err = uconn.uLoadSession()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = uconn.MarshalClientHello()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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.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)
|
|
}
|
|
|
|
// 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() }()
|
|
|
|
sessionIsLocked := c.utls.sessionController.isSessionLocked()
|
|
|
|
// 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[:]
|
|
}
|
|
|
|
var (
|
|
session *SessionState
|
|
earlySecret []byte
|
|
binderKey []byte
|
|
)
|
|
if !sessionIsLocked {
|
|
// [uTLS section ends]
|
|
|
|
session, earlySecret, binderKey, err = c.loadSession(hello)
|
|
|
|
// [uTLS section start]
|
|
} else {
|
|
session = c.HandshakeState.Session
|
|
earlySecret = c.HandshakeState.State13.EarlySecret
|
|
binderKey = c.HandshakeState.State13.BinderKey
|
|
}
|
|
// [uTLS section ends]
|
|
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)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
if _, err := c.writeHandshakeRecord(hello, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
if hello.earlyData {
|
|
suite := cipherSuiteTLS13ByID(session.cipherSuite)
|
|
transcript := suite.hash.New()
|
|
if err := transcriptMsg(hello, transcript); err != nil {
|
|
return err
|
|
}
|
|
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
|
|
c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
|
|
}
|
|
|
|
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
|
|
hs13.keySharesParams = NewKeySharesParameters()
|
|
if !sessionIsLocked {
|
|
hs13.earlySecret = earlySecret
|
|
hs13.binderKey = binderKey
|
|
hs13.session = session
|
|
}
|
|
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
|
|
hs12.session = session
|
|
err = hs12.handshake()
|
|
if handshakeState := hs12.toPublic12(); handshakeState != nil {
|
|
c.HandshakeState = *handshakeState
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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 {
|
|
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
|
|
}
|
|
|
|
type utlsConnExtraFields struct {
|
|
// Application Settings (ALPS)
|
|
hasApplicationSettings bool
|
|
peerApplicationSettings []byte
|
|
localApplicationSettings []byte
|
|
|
|
sessionController *sessionController
|
|
}
|