Browse Source

Update vendored refraction-networking/utls

Rod Hynes 4 years ago
parent
commit
227d23d842

+ 20 - 0
vendor/github.com/refraction-networking/utls/README.md

@@ -102,6 +102,26 @@ you can set UConn.HandshakeStateBuilt = true, and marshal clientHello into UConn
 In this case you will be responsible for modifying other parts of Config and ClientHelloMsg to reflect your setup
 In this case you will be responsible for modifying other parts of Config and ClientHelloMsg to reflect your setup
 and not confuse "crypto/tls", which will be processing response from server.
 and not confuse "crypto/tls", which will be processing response from server.
 
 
+### Fingerprinting Captured Client Hello
+You can use a captured client hello to generate new ones that mimic/have the same properties as the original.
+The generated client hellos _should_ look like they were generated from the same client software as the original fingerprinted bytes.
+In order to do this:
+1) Create a `ClientHelloSpec` from the raw bytes of the original client hello
+2) Use `HelloCustom` as an argument for `UClient()` to get empty config
+3) Use `ApplyPreset` with the generated `ClientHelloSpec` to set the appropriate connection properties
+```
+uConn := UClient(&net.TCPConn{}, nil, HelloCustom)
+fingerprinter := &Fingerprinter{}
+generatedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes)
+if err != nil {
+  panic("fingerprinting failed: %v", err)
+}
+if err := uConn.ApplyPreset(generatedSpec); err != nil {
+  panic("applying generated spec failed: %v", err)
+}
+```
+The `rawCapturedClientHelloBytes` should be the full tls record, including the record type/version/length header.
+
 ## Roller
 ## Roller
 
 
 A simple wrapper, that allows to easily use multiple latest(auto-updated) fingerprints.
 A simple wrapper, that allows to easily use multiple latest(auto-updated) fingerprints.

+ 16 - 1
vendor/github.com/refraction-networking/utls/ticket.go

@@ -171,7 +171,20 @@ func (c *Conn) encryptTicket(state []byte) ([]byte, error) {
 	return encrypted, nil
 	return encrypted, nil
 }
 }
 
 
+// [uTLS] changed to use exported DecryptTicketWith func below
 func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey bool) {
 func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey bool) {
+	tks := ticketKeys(c.config.ticketKeys()).ToPublic()
+	return DecryptTicketWith(encrypted, tks)
+}
+
+// DecryptTicketWith decrypts an encrypted session ticket
+// using a TicketKeys (ie []TicketKey) struct
+//
+// usedOldKey will be true if the key used for decryption is
+// not the first in the []TicketKey slice
+//
+// [uTLS] changed to be made public and take a TicketKeys instead of use a Conn receiver
+func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, usedOldKey bool) {
 	if len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
 	if len(encrypted) < ticketKeyNameLen+aes.BlockSize+sha256.Size {
 		return nil, false
 		return nil, false
 	}
 	}
@@ -181,7 +194,9 @@ func (c *Conn) decryptTicket(encrypted []byte) (plaintext []byte, usedOldKey boo
 	macBytes := encrypted[len(encrypted)-sha256.Size:]
 	macBytes := encrypted[len(encrypted)-sha256.Size:]
 	ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size]
 	ciphertext := encrypted[ticketKeyNameLen+aes.BlockSize : len(encrypted)-sha256.Size]
 
 
-	keys := c.config.ticketKeys()
+	// keys := c.config.ticketKeys() // [uTLS] keys are received as a function argument
+
+	keys := tks.ToPrivate()
 	keyIndex := -1
 	keyIndex := -1
 	for i, candidateKey := range keys {
 	for i, candidateKey := range keys {
 		if bytes.Equal(keyName, candidateKey.keyName[:]) {
 		if bytes.Equal(keyName, candidateKey.keyName[:]) {

+ 15 - 0
vendor/github.com/refraction-networking/utls/u_common.go

@@ -39,6 +39,7 @@ const (
 
 
 	FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA  = uint16(0x0033)
 	FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA  = uint16(0x0033)
 	FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA  = uint16(0x0039)
 	FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA  = uint16(0x0039)
+	FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384  = uint16(0x009f)
 	FAKE_TLS_RSA_WITH_RC4_128_MD5          = uint16(0x0004)
 	FAKE_TLS_RSA_WITH_RC4_128_MD5          = uint16(0x0004)
 	FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
 	FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
 )
 )
@@ -161,6 +162,20 @@ var (
 // https://tools.ietf.org/html/draft-ietf-tls-grease-01
 // https://tools.ietf.org/html/draft-ietf-tls-grease-01
 const GREASE_PLACEHOLDER = 0x0a0a
 const GREASE_PLACEHOLDER = 0x0a0a
 
 
+func isGREASEUint16(v uint16) bool {
+	// First byte is same as second byte
+	// and lowest nibble is 0xa
+	return ((v >> 8) == v&0xff) && v&0xf == 0xa
+}
+
+func unGREASEUint16(v uint16) uint16 {
+	if isGREASEUint16(v) {
+		return GREASE_PLACEHOLDER
+	} else {
+		return v
+	}
+}
+
 // utlsMacSHA384 returns a SHA-384 based MAC. These are only supported in TLS 1.2
 // utlsMacSHA384 returns a SHA-384 based MAC. These are only supported in TLS 1.2
 // so the given version is ignored.
 // so the given version is ignored.
 func utlsMacSHA384(version uint16, key []byte) macFunction {
 func utlsMacSHA384(version uint16, key []byte) macFunction {

+ 360 - 0
vendor/github.com/refraction-networking/utls/u_fingerprinter.go

@@ -0,0 +1,360 @@
+// 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 (
+	"errors"
+	"fmt"
+	"strings"
+
+	"golang.org/x/crypto/cryptobyte"
+)
+
+// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
+type Fingerprinter struct {
+	// KeepPSK will ensure that the PreSharedKey extension is passed along into the resulting ClientHelloSpec as-is
+	KeepPSK bool
+	// AllowBluntMimicry will ensure that unknown extensions are
+	// passed along into the resulting ClientHelloSpec as-is
+	// It will not ensure that the PSK is passed along, if you require that, use KeepPSK
+	// WARNING: there could be numerous subtle issues with ClientHelloSpecs
+	// that are generated with this flag which could compromise security and/or mimicry
+	AllowBluntMimicry bool
+	// AlwaysAddPadding will always add a UtlsPaddingExtension with BoringPaddingStyle
+	// at the end of the extensions list if it isn't found in the fingerprinted hello.
+	// This could be useful in scenarios where the hello you are fingerprinting does not
+	// have any padding, but you suspect that other changes you make to the final hello
+	// (including things like different SNI lengths) would cause padding to be necessary
+	AlwaysAddPadding bool
+}
+
+// FingerprintClientHello returns a ClientHelloSpec which is based on the
+// ClientHello that is passed in as the data argument
+//
+// If the ClientHello passed in has extensions that are not recognized or cannot be handled
+// it will return a non-nil error and a nil *ClientHelloSpec value
+//
+// The data should be the full tls record, including the record type/version/length header
+// as well as the handshake type/length/version header
+// https://tools.ietf.org/html/rfc5246#section-6.2
+// https://tools.ietf.org/html/rfc5246#section-7.4
+func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, error) {
+	clientHelloSpec := &ClientHelloSpec{}
+	s := cryptobyte.String(data)
+
+	var contentType uint8
+	var recordVersion uint16
+	if !s.ReadUint8(&contentType) || // record type
+		!s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length
+		return nil, errors.New("unable to read record type, version, and length")
+	}
+
+	if recordType(contentType) != recordTypeHandshake {
+		return nil, errors.New("record is not a handshake")
+	}
+
+	var handshakeVersion uint16
+	var handshakeType uint8
+
+	if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length
+		!s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random
+		return nil, errors.New("unable to read handshake message type, length, and random")
+	}
+
+	if handshakeType != typeClientHello {
+		return nil, errors.New("handshake message is not a ClientHello")
+	}
+
+	clientHelloSpec.TLSVersMin = recordVersion
+	clientHelloSpec.TLSVersMax = handshakeVersion
+
+	var ignoredSessionID cryptobyte.String
+	if !s.ReadUint8LengthPrefixed(&ignoredSessionID) {
+		return nil, errors.New("unable to read session id")
+	}
+
+	var cipherSuitesBytes cryptobyte.String
+	if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
+		return nil, errors.New("unable to read ciphersuites")
+	}
+	cipherSuites := []uint16{}
+	for !cipherSuitesBytes.Empty() {
+		var suite uint16
+		if !cipherSuitesBytes.ReadUint16(&suite) {
+			return nil, errors.New("unable to read ciphersuite")
+		}
+		cipherSuites = append(cipherSuites, unGREASEUint16(suite))
+	}
+	clientHelloSpec.CipherSuites = cipherSuites
+
+	if !readUint8LengthPrefixed(&s, &clientHelloSpec.CompressionMethods) {
+		return nil, errors.New("unable to read compression methods")
+	}
+
+	if s.Empty() {
+		// ClientHello is optionally followed by extension data
+		return clientHelloSpec, nil
+	}
+
+	var extensions cryptobyte.String
+	if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() {
+		return nil, errors.New("unable to read extensions data")
+	}
+
+	for !extensions.Empty() {
+		var extension uint16
+		var extData cryptobyte.String
+		if !extensions.ReadUint16(&extension) ||
+			!extensions.ReadUint16LengthPrefixed(&extData) {
+			return nil, errors.New("unable to read extension data")
+		}
+
+		switch extension {
+		case extensionServerName:
+			// RFC 6066, Section 3
+			var nameList cryptobyte.String
+			if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() {
+				return nil, errors.New("unable to read server name extension data")
+			}
+			var serverName string
+			for !nameList.Empty() {
+				var nameType uint8
+				var serverNameBytes cryptobyte.String
+				if !nameList.ReadUint8(&nameType) ||
+					!nameList.ReadUint16LengthPrefixed(&serverNameBytes) ||
+					serverNameBytes.Empty() {
+					return nil, errors.New("unable to read server name extension data")
+				}
+				if nameType != 0 {
+					continue
+				}
+				if len(serverName) != 0 {
+					return nil, errors.New("multiple names of the same name_type in server name extension are prohibited")
+				}
+				serverName = string(serverNameBytes)
+				if strings.HasSuffix(serverName, ".") {
+					return nil, errors.New("SNI value may not include a trailing dot")
+				}
+
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{})
+
+			}
+		case extensionNextProtoNeg:
+			// draft-agl-tls-nextprotoneg-04
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &NPNExtension{})
+
+		case extensionStatusRequest:
+			// RFC 4366, Section 3.6
+			var statusType uint8
+			var ignored cryptobyte.String
+			if !extData.ReadUint8(&statusType) ||
+				!extData.ReadUint16LengthPrefixed(&ignored) ||
+				!extData.ReadUint16LengthPrefixed(&ignored) {
+				return nil, errors.New("unable to read status request extension data")
+			}
+
+			if statusType == statusTypeOCSP {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &StatusRequestExtension{})
+			} else {
+				return nil, errors.New("status request extension statusType is not statusTypeOCSP")
+			}
+
+		case extensionSupportedCurves:
+			// RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7
+			var curvesBytes cryptobyte.String
+			if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() {
+				return nil, errors.New("unable to read supported curves extension data")
+			}
+			curves := []CurveID{}
+			for !curvesBytes.Empty() {
+				var curve uint16
+				if !curvesBytes.ReadUint16(&curve) {
+					return nil, errors.New("unable to read supported curves extension data")
+				}
+				curves = append(curves, CurveID(unGREASEUint16(curve)))
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedCurvesExtension{curves})
+
+		case extensionSupportedPoints:
+			// RFC 4492, Section 5.1.2
+			supportedPoints := []uint8{}
+			if !readUint8LengthPrefixed(&extData, &supportedPoints) ||
+				len(supportedPoints) == 0 {
+				return nil, errors.New("unable to read supported points extension data")
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedPointsExtension{supportedPoints})
+
+		case extensionSessionTicket:
+			// RFC 5077, Section 3.2
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SessionTicketExtension{})
+
+		case extensionSignatureAlgorithms:
+			// RFC 5246, Section 7.4.1.4.1
+			var sigAndAlgs cryptobyte.String
+			if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() {
+				return nil, errors.New("unable to read signature algorithms extension data")
+			}
+			supportedSignatureAlgorithms := []SignatureScheme{}
+			for !sigAndAlgs.Empty() {
+				var sigAndAlg uint16
+				if !sigAndAlgs.ReadUint16(&sigAndAlg) {
+					return nil, errors.New("unable to read signature algorithms extension data")
+				}
+				supportedSignatureAlgorithms = append(
+					supportedSignatureAlgorithms, SignatureScheme(sigAndAlg))
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SignatureAlgorithmsExtension{supportedSignatureAlgorithms})
+
+		case extensionSignatureAlgorithmsCert:
+			// RFC 8446, Section 4.2.3
+			if f.AllowBluntMimicry {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+			} else {
+				return nil, errors.New("unsupported extension SignatureAlgorithmsCert")
+			}
+
+		case extensionRenegotiationInfo:
+			// RFC 5746, Section 3.2
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &RenegotiationInfoExtension{RenegotiateOnceAsClient})
+
+		case extensionALPN:
+			// RFC 7301, Section 3.1
+			var protoList cryptobyte.String
+			if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() {
+				return nil, errors.New("unable to read ALPN extension data")
+			}
+			alpnProtocols := []string{}
+			for !protoList.Empty() {
+				var proto cryptobyte.String
+				if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() {
+					return nil, errors.New("unable to read ALPN extension data")
+				}
+				alpnProtocols = append(alpnProtocols, string(proto))
+
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ALPNExtension{alpnProtocols})
+
+		case extensionSCT:
+			// RFC 6962, Section 3.3.1
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SCTExtension{})
+
+		case extensionSupportedVersions:
+			// RFC 8446, Section 4.2.1
+			var versList cryptobyte.String
+			if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() {
+				return nil, errors.New("unable to read supported versions extension data")
+			}
+			supportedVersions := []uint16{}
+			for !versList.Empty() {
+				var vers uint16
+				if !versList.ReadUint16(&vers) {
+					return nil, errors.New("unable to read supported versions extension data")
+				}
+				supportedVersions = append(supportedVersions, unGREASEUint16(vers))
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedVersionsExtension{supportedVersions})
+			// If SupportedVersionsExtension is present, use that instead of record+handshake versions
+			clientHelloSpec.TLSVersMin = 0
+			clientHelloSpec.TLSVersMax = 0
+
+		case extensionKeyShare:
+			// RFC 8446, Section 4.2.8
+			var clientShares cryptobyte.String
+			if !extData.ReadUint16LengthPrefixed(&clientShares) {
+				return nil, errors.New("unable to read key share extension data")
+			}
+			keyShares := []KeyShare{}
+			for !clientShares.Empty() {
+				var ks KeyShare
+				var group uint16
+				if !clientShares.ReadUint16(&group) ||
+					!readUint16LengthPrefixed(&clientShares, &ks.Data) ||
+					len(ks.Data) == 0 {
+					return nil, errors.New("unable to read key share extension data")
+				}
+				ks.Group = CurveID(unGREASEUint16(group))
+				// if not GREASE, key share data will be discarded as it should
+				// be generated per connection
+				if ks.Group != GREASE_PLACEHOLDER {
+					ks.Data = nil
+				}
+				keyShares = append(keyShares, ks)
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &KeyShareExtension{keyShares})
+
+		case extensionPSKModes:
+			// RFC 8446, Section 4.2.9
+			// TODO: PSK Modes have their own form of GREASE-ing which is not currently implemented
+			// the current functionality will NOT re-GREASE/re-randomize these values when using a fingerprinted spec
+			// https://github.com/refraction-networking/utls/pull/58#discussion_r522354105
+			// https://tools.ietf.org/html/draft-ietf-tls-grease-01#section-2
+			pskModes := []uint8{}
+			if !readUint8LengthPrefixed(&extData, &pskModes) {
+				return nil, errors.New("unable to read PSK extension data")
+			}
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &PSKKeyExchangeModesExtension{pskModes})
+
+		case utlsExtensionExtendedMasterSecret:
+			// https://tools.ietf.org/html/rfc7627
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsExtendedMasterSecretExtension{})
+
+		case utlsExtensionPadding:
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
+
+		case fakeExtensionChannelID, fakeCertCompressionAlgs, fakeRecordSizeLimit:
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+
+		case extensionPreSharedKey:
+			// RFC 8446, Section 4.2.11
+			if f.KeepPSK {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+			} else {
+				return nil, errors.New("unsupported extension PreSharedKey")
+			}
+
+		case extensionCookie:
+			// RFC 8446, Section 4.2.2
+			if f.AllowBluntMimicry {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+			} else {
+				return nil, errors.New("unsupported extension Cookie")
+			}
+
+		case extensionEarlyData:
+			// RFC 8446, Section 4.2.10
+			if f.AllowBluntMimicry {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+			} else {
+				return nil, errors.New("unsupported extension EarlyData")
+			}
+
+		default:
+			if isGREASEUint16(extension) {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsGREASEExtension{unGREASEUint16(extension), extData})
+			} else if f.AllowBluntMimicry {
+				clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData})
+			} else {
+				return nil, fmt.Errorf("unsupported extension %#x", extension)
+			}
+
+			continue
+		}
+	}
+
+	if f.AlwaysAddPadding {
+		alreadyHasPadding := false
+		for _, ext := range clientHelloSpec.Extensions {
+			if _, ok := ext.(*UtlsPaddingExtension); ok {
+				alreadyHasPadding = true
+				break
+			}
+		}
+		if !alreadyHasPadding {
+			clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
+		}
+	}
+
+	return clientHelloSpec, nil
+}

+ 10 - 0
vendor/github.com/refraction-networking/utls/u_parrots.go

@@ -617,6 +617,9 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
 	uconn.Extensions = make([]TLSExtension, len(p.Extensions))
 	uconn.Extensions = make([]TLSExtension, len(p.Extensions))
 	copy(uconn.Extensions, p.Extensions)
 	copy(uconn.Extensions, p.Extensions)
 
 
+	// Check whether NPN extension actually exists
+	var haveNPN bool
+
 	// reGrease, and point things to each other
 	// reGrease, and point things to each other
 	for _, e := range uconn.Extensions {
 	for _, e := range uconn.Extensions {
 		switch ext := e.(type) {
 		switch ext := e.(type) {
@@ -681,8 +684,15 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
 					ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version)
 					ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version)
 				}
 				}
 			}
 			}
+		case *NPNExtension:
+			haveNPN = true
 		}
 		}
 	}
 	}
+
+	// The default golang behavior in makeClientHello always sets NextProtoNeg if NextProtos is set,
+	// but NextProtos is also used by ALPN and our spec nmay not actually have a NPN extension
+	hello.NextProtoNeg = haveNPN
+
 	return nil
 	return nil
 }
 }
 
 

+ 59 - 0
vendor/github.com/refraction-networking/utls/u_public.go

@@ -421,6 +421,16 @@ func (chm *clientHelloMsg) getPublicPtr() *ClientHelloMsg {
 	}
 	}
 }
 }
 
 
+// UnmarshalClientHello allows external code to parse raw client hellos.
+// It returns nil on failure.
+func UnmarshalClientHello(data []byte) *ClientHelloMsg {
+	m := &clientHelloMsg{}
+	if m.unmarshal(data) {
+		return m.getPublicPtr()
+	}
+	return nil
+}
+
 // A CipherSuite is a specific combination of key agreement, cipher and MAC
 // A CipherSuite is a specific combination of key agreement, cipher and MAC
 // function. All cipher suites currently assume RSA key agreement.
 // function. All cipher suites currently assume RSA key agreement.
 type CipherSuite struct {
 type CipherSuite struct {
@@ -612,3 +622,52 @@ func (css *ClientSessionState) SetServerCertificates(ServerCertificates []*x509.
 func (css *ClientSessionState) SetVerifiedChains(VerifiedChains [][]*x509.Certificate) {
 func (css *ClientSessionState) SetVerifiedChains(VerifiedChains [][]*x509.Certificate) {
 	css.verifiedChains = VerifiedChains
 	css.verifiedChains = VerifiedChains
 }
 }
+
+// TicketKey is the internal representation of a session ticket key.
+type TicketKey struct {
+	// KeyName is an opaque byte string that serves to identify the session
+	// ticket key. It's exposed as plaintext in every session ticket.
+	KeyName [ticketKeyNameLen]byte
+	AesKey  [16]byte
+	HmacKey [16]byte
+}
+
+type TicketKeys []TicketKey
+type ticketKeys []ticketKey
+
+func TicketKeyFromBytes(b [32]byte) TicketKey {
+	tk := ticketKeyFromBytes(b)
+	return tk.ToPublic()
+}
+
+func (tk ticketKey) ToPublic() TicketKey {
+	return TicketKey{
+		KeyName: tk.keyName,
+		AesKey:  tk.aesKey,
+		HmacKey: tk.hmacKey,
+	}
+}
+
+func (TK TicketKey) ToPrivate() ticketKey {
+	return ticketKey{
+		keyName: TK.KeyName,
+		aesKey:  TK.AesKey,
+		hmacKey: TK.HmacKey,
+	}
+}
+
+func (tks ticketKeys) ToPublic() []TicketKey {
+	var TKS []TicketKey
+	for _, ks := range tks {
+		TKS = append(TKS, ks.ToPublic())
+	}
+	return TKS
+}
+
+func (TKS TicketKeys) ToPrivate() []ticketKey {
+	var tks []ticketKey
+	for _, TK := range TKS {
+		tks = append(tks, TK.ToPrivate())
+	}
+	return tks
+}

+ 3 - 3
vendor/vendor.json

@@ -586,10 +586,10 @@
 			"revisionTime": "2021-06-04T20:39:09Z"
 			"revisionTime": "2021-06-04T20:39:09Z"
 		},
 		},
 		{
 		{
-			"checksumSHA1": "OagdWaWcbCBQZR5bBGgGaK3nddE=",
+			"checksumSHA1": "jOxlnqvKSKn1SIkA5ldRe5lxqAc=",
 			"path": "github.com/refraction-networking/utls",
 			"path": "github.com/refraction-networking/utls",
-			"revision": "186025ac7b77465439618d1aeb2a5e444714d1cc",
-			"revisionTime": "2020-07-29T01:25:36Z"
+			"revision": "0b2885c8c0d4467cfe98136748a9d011d0b8fff0",
+			"revisionTime": "2021-07-13T16:56:36Z"
 		},
 		},
 		{
 		{
 			"checksumSHA1": "Fn9JW8u40ABN9Uc9wuvquuyOB+8=",
 			"checksumSHA1": "Fn9JW8u40ABN9Uc9wuvquuyOB+8=",