mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-01 19:17:36 +03:00
* 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>
789 lines
23 KiB
Go
789 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"
|
|
"crypto/ecdh"
|
|
"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, 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
|
|
} 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
|
|
}
|