mirror of
https://github.com/SagerNet/sing-shadowtls.git
synced 2025-04-02 19:57:35 +03:00
285 lines
7.5 KiB
Go
285 lines
7.5 KiB
Go
// Copyright 2024 The Go Authors. 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"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-shadowtls/internal/hpke"
|
|
"github.com/sagernet/sing/common"
|
|
|
|
"golang.org/x/crypto/cryptobyte"
|
|
)
|
|
|
|
type echCipher struct {
|
|
KDFID uint16
|
|
AEADID uint16
|
|
}
|
|
|
|
type echExtension struct {
|
|
Type uint16
|
|
Data []byte
|
|
}
|
|
|
|
type echConfig struct {
|
|
raw []byte
|
|
|
|
Version uint16
|
|
Length uint16
|
|
|
|
ConfigID uint8
|
|
KemID uint16
|
|
PublicKey []byte
|
|
SymmetricCipherSuite []echCipher
|
|
|
|
MaxNameLength uint8
|
|
PublicName []byte
|
|
Extensions []echExtension
|
|
}
|
|
|
|
var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")
|
|
|
|
// parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
|
|
// slice of parsed ECHConfigs, in the same order they were parsed, or an error
|
|
// if the list is malformed.
|
|
func parseECHConfigList(data []byte) ([]echConfig, error) {
|
|
s := cryptobyte.String(data)
|
|
// Skip the length prefix
|
|
var length uint16
|
|
if !s.ReadUint16(&length) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if length != uint16(len(data)-2) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
var configs []echConfig
|
|
for len(s) > 0 {
|
|
var ec echConfig
|
|
ec.raw = []byte(s)
|
|
if !s.ReadUint16(&ec.Version) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if !s.ReadUint16(&ec.Length) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if len(ec.raw) < int(ec.Length)+4 {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
ec.raw = ec.raw[:ec.Length+4]
|
|
if ec.Version != extensionEncryptedClientHello {
|
|
s.Skip(int(ec.Length))
|
|
continue
|
|
}
|
|
if !s.ReadUint8(&ec.ConfigID) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if !s.ReadUint16(&ec.KemID) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&ec.PublicKey)) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
var cipherSuites cryptobyte.String
|
|
if !s.ReadUint16LengthPrefixed(&cipherSuites) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
for !cipherSuites.Empty() {
|
|
var c echCipher
|
|
if !cipherSuites.ReadUint16(&c.KDFID) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if !cipherSuites.ReadUint16(&c.AEADID) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
|
|
}
|
|
if !s.ReadUint8(&ec.MaxNameLength) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
var publicName cryptobyte.String
|
|
if !s.ReadUint8LengthPrefixed(&publicName) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
ec.PublicName = publicName
|
|
var extensions cryptobyte.String
|
|
if !s.ReadUint16LengthPrefixed(&extensions) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
for !extensions.Empty() {
|
|
var e echExtension
|
|
if !extensions.ReadUint16(&e.Type) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
|
|
return nil, errMalformedECHConfig
|
|
}
|
|
ec.Extensions = append(ec.Extensions, e)
|
|
}
|
|
|
|
configs = append(configs, ec)
|
|
}
|
|
return configs, nil
|
|
}
|
|
|
|
func pickECHConfig(list []echConfig) *echConfig {
|
|
for _, ec := range list {
|
|
if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
|
|
continue
|
|
}
|
|
var validSCS bool
|
|
for _, cs := range ec.SymmetricCipherSuite {
|
|
if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
|
|
continue
|
|
}
|
|
if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
|
|
continue
|
|
}
|
|
validSCS = true
|
|
break
|
|
}
|
|
if !validSCS {
|
|
continue
|
|
}
|
|
if !validDNSName(string(ec.PublicName)) {
|
|
continue
|
|
}
|
|
var unsupportedExt bool
|
|
for _, ext := range ec.Extensions {
|
|
// If high order bit is set to 1 the extension is mandatory.
|
|
// Since we don't support any extensions, if we see a mandatory
|
|
// bit, we skip the config.
|
|
if ext.Type&uint16(1<<15) != 0 {
|
|
unsupportedExt = true
|
|
}
|
|
}
|
|
if unsupportedExt {
|
|
continue
|
|
}
|
|
return &ec
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
|
|
for _, s := range suites {
|
|
// NOTE: all of the supported AEADs and KDFs are fine, rather than
|
|
// imposing some sort of preference here, we just pick the first valid
|
|
// suite.
|
|
if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
|
|
continue
|
|
}
|
|
if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
|
|
continue
|
|
}
|
|
return s, nil
|
|
}
|
|
return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
|
|
}
|
|
|
|
func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
|
|
h, err := inner.marshalMsg(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h = h[4:] // strip four byte prefix
|
|
|
|
var paddingLen int
|
|
if inner.serverName != "" {
|
|
paddingLen = common.Max(0, maxNameLength-len(inner.serverName))
|
|
} else {
|
|
paddingLen = maxNameLength + 9
|
|
}
|
|
paddingLen = 31 - ((len(h) + paddingLen - 1) % 32)
|
|
|
|
return append(h, make([]byte, paddingLen)...), nil
|
|
}
|
|
|
|
func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
|
|
var b cryptobyte.Builder
|
|
b.AddUint8(0) // outer
|
|
b.AddUint16(kdfID)
|
|
b.AddUint16(aeadID)
|
|
b.AddUint8(id)
|
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(encodedKey) })
|
|
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(payload) })
|
|
return b.Bytes()
|
|
}
|
|
|
|
func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echContext, useKey bool) error {
|
|
var encapKey []byte
|
|
if useKey {
|
|
encapKey = ech.encapsulatedKey
|
|
}
|
|
encodedInner, err := encodeInnerClientHello(inner, int(ech.config.MaxNameLength))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// NOTE: the tag lengths for all of the supported AEADs are the same (16
|
|
// bytes), so we have hardcoded it here. If we add support for another AEAD
|
|
// with a different tag length, we will need to change this.
|
|
encryptedLen := len(encodedInner) + 16 // AEAD tag length
|
|
outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, make([]byte, encryptedLen))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serializedOuter, err := outer.marshal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serializedOuter = serializedOuter[4:] // strip the four byte prefix
|
|
encryptedInner, err := ech.hpkeContext.Seal(serializedOuter, encodedInner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, encryptedInner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validDNSName is a rather rudimentary check for the validity of a DNS name.
|
|
// This is used to check if the public_name in a ECHConfig is valid when we are
|
|
// picking a config. This can be somewhat lax because even if we pick a
|
|
// valid-looking name, the DNS layer will later reject it anyway.
|
|
func validDNSName(name string) bool {
|
|
if len(name) > 253 {
|
|
return false
|
|
}
|
|
labels := strings.Split(name, ".")
|
|
if len(labels) <= 1 {
|
|
return false
|
|
}
|
|
for _, l := range labels {
|
|
labelLen := len(l)
|
|
if labelLen == 0 {
|
|
return false
|
|
}
|
|
for i, r := range l {
|
|
if r == '-' && (i == 0 || i == labelLen-1) {
|
|
return false
|
|
}
|
|
if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '-' {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ECHRejectionError is the error type returned when ECH is rejected by a remote
|
|
// server. If the server offered a ECHConfigList to use for retries, the
|
|
// RetryConfigList field will contain this list.
|
|
//
|
|
// The client may treat an ECHRejectionError with an empty set of RetryConfigs
|
|
// as a secure signal from the server.
|
|
type ECHRejectionError struct {
|
|
RetryConfigList []byte
|
|
}
|
|
|
|
func (e *ECHRejectionError) Error() string {
|
|
return "tls: server rejected ECH"
|
|
}
|