utls/u_conn.go
Gaukas Wang 6c1a910019 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>
2023-08-26 14:23:54 -06:00

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
}