gitlab.com/go-extension/tls@v0.0.0-20240304171319-e6745021905e/ech_provider.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tls
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/cloudflare/circl/hpke"
    12  	"github.com/cloudflare/circl/kem"
    13  
    14  	"golang.org/x/crypto/cryptobyte"
    15  )
    16  
    17  // ECHProvider specifies the interface of an ECH service provider that decrypts
    18  // the ECH payload on behalf of the client-facing server. It also defines the
    19  // set of acceptable ECH configurations.
    20  type ECHProvider interface {
    21  	// GetDecryptionContext attempts to construct the HPKE context used by the
    22  	// client-facing server for decryption. (See draft-irtf-cfrg-hpke-07,
    23  	// Section 5.2.)
    24  	//
    25  	// handle encodes the parameters of the client's "encrypted_client_hello"
    26  	// extension that are needed to construct the context. Since
    27  	// draft-ietf-tls-esni-10 these are the ECH cipher suite, the identity of
    28  	// the ECH configuration, and the encapsulated key.
    29  	//
    30  	// version is the version of ECH indicated by the client.
    31  	//
    32  	// res.Status == ECHProviderStatusSuccess indicates the call was successful
    33  	// and the caller may proceed. res.Context is set.
    34  	//
    35  	// res.Status == ECHProviderStatusReject indicates the caller must reject
    36  	// ECH. res.RetryConfigs may be set.
    37  	//
    38  	// res.Status == ECHProviderStatusAbort indicates the caller should abort
    39  	// the handshake.  Note that, in some cases, it's appropriate to reject
    40  	// rather than abort. In particular, aborting with "illegal_parameter" might
    41  	// "stick out". res.Alert and res.Error are set.
    42  	GetDecryptionContext(handle []byte, version uint16) (res ECHProviderResult)
    43  }
    44  
    45  // ECHProviderStatus is the status of the ECH provider's response.
    46  type ECHProviderStatus uint
    47  
    48  const (
    49  	ECHProviderSuccess ECHProviderStatus = iota
    50  	ECHProviderReject
    51  	ECHProviderAbort
    52  
    53  	errHPKEInvalidPublicKey = "hpke: invalid KEM public key"
    54  )
    55  
    56  // ECHProviderResult represents the result of invoking the ECH provider.
    57  type ECHProviderResult struct {
    58  	Status ECHProviderStatus
    59  
    60  	// Alert is the TLS alert sent by the caller when aborting the handshake.
    61  	Alert uint8
    62  
    63  	// Error is the error propagated by the caller when aborting the handshake.
    64  	Error error
    65  
    66  	// RetryConfigs is the sequence of ECH configs to offer to the client for
    67  	// retrying the handshake. This may be set in case of success or rejection.
    68  	RetryConfigs []byte
    69  
    70  	// Context is the server's HPKE context. This is set if ECH is not rejected
    71  	// by the provider and no error was reported. The data has the following
    72  	// format (in TLS syntax):
    73  	//
    74  	// enum { sealer(0), opener(1) } HpkeRole;
    75  	//
    76  	// struct {
    77  	//     HpkeRole role;
    78  	//     HpkeKemId kem_id;   // as defined in draft-irtf-cfrg-hpke-07
    79  	//     HpkeKdfId kdf_id;   // as defined in draft-irtf-cfrg-hpke-07
    80  	//     HpkeAeadId aead_id; // as defined in draft-irtf-cfrg-hpke-07
    81  	//     opaque exporter_secret<0..255>;
    82  	//     opaque key<0..255>;
    83  	//     opaque base_nonce<0..255>;
    84  	//     opaque seq<0..255>;
    85  	// } HpkeContext;
    86  	Context []byte
    87  }
    88  
    89  // EXP_ECHKeySet implements the ECHProvider interface for a sequence of ECH keys.
    90  //
    91  // NOTE: This API is EXPERIMENTAL and subject to change.
    92  type EXP_ECHKeySet struct {
    93  	// The serialized ECHConfigs, in order of the server's preference.
    94  	configs []byte
    95  
    96  	// Maps a configuration identifier to its secret key.
    97  	sk map[uint8]EXP_ECHKey
    98  }
    99  
   100  // EXP_NewECHKeySet constructs an EXP_ECHKeySet.
   101  func EXP_NewECHKeySet(keys []EXP_ECHKey) (*EXP_ECHKeySet, error) {
   102  	if len(keys) > 255 {
   103  		return nil, fmt.Errorf("tls: ech provider: unable to support more than 255 ECH configurations at once")
   104  	}
   105  
   106  	keySet := new(EXP_ECHKeySet)
   107  	keySet.sk = make(map[uint8]EXP_ECHKey)
   108  	configs := make([]byte, 0)
   109  	for _, key := range keys {
   110  		if _, ok := keySet.sk[key.config.configId]; ok {
   111  			return nil, fmt.Errorf("tls: ech provider: ECH config conflict for configId %d", key.config.configId)
   112  		}
   113  
   114  		keySet.sk[key.config.configId] = key
   115  		configs = append(configs, key.config.raw...)
   116  	}
   117  
   118  	var b cryptobyte.Builder
   119  	b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
   120  		b.AddBytes(configs)
   121  	})
   122  	keySet.configs = b.BytesOrPanic()
   123  
   124  	return keySet, nil
   125  }
   126  
   127  // GetDecryptionContext is required by the ECHProvider interface.
   128  func (keySet *EXP_ECHKeySet) GetDecryptionContext(rawHandle []byte, version uint16) (res ECHProviderResult) {
   129  	// Propagate retry configurations regardless of the result. The caller sends
   130  	// these to the clients only if it rejects.
   131  	res.RetryConfigs = keySet.configs
   132  
   133  	// Ensure we know how to proceed, i.e., the caller has indicated a supported
   134  	// version of ECH. Currently only draft-ietf-tls-esni-13 is supported.
   135  	if version != extensionECH {
   136  		res.Status = ECHProviderAbort
   137  		res.Alert = uint8(alertInternalError)
   138  		res.Error = errors.New("version not supported")
   139  		return // Abort
   140  	}
   141  
   142  	// Parse the handle.
   143  	s := cryptobyte.String(rawHandle)
   144  	handle := new(echContextHandle)
   145  	if !echReadContextHandle(&s, handle) || !s.Empty() {
   146  		// This is the result of a client-side error. However, aborting with
   147  		// "illegal_parameter" would stick out, so we reject instead.
   148  		res.Status = ECHProviderReject
   149  		res.RetryConfigs = keySet.configs
   150  		return // Reject
   151  	}
   152  	handle.raw = rawHandle
   153  
   154  	// Look up the secret key for the configuration indicated by the client.
   155  	key, ok := keySet.sk[handle.configId]
   156  	if !ok {
   157  		res.Status = ECHProviderReject
   158  		res.RetryConfigs = keySet.configs
   159  		return // Reject
   160  	}
   161  
   162  	// Ensure that support for the selected ciphersuite is indicated by the
   163  	// configuration.
   164  	suite := handle.suite
   165  	if !key.config.isPeerCipherSuiteSupported(suite) {
   166  		// This is the result of a client-side error. However, aborting with
   167  		// "illegal_parameter" would stick out, so we reject instead.
   168  		res.Status = ECHProviderReject
   169  		res.RetryConfigs = keySet.configs
   170  		return // Reject
   171  	}
   172  
   173  	// Ensure the version indicated by the client matches the version supported
   174  	// by the configuration.
   175  	if version != key.config.version {
   176  		// This is the result of a client-side error. However, aborting with
   177  		// "illegal_parameter" would stick out, so we reject instead.
   178  		res.Status = ECHProviderReject
   179  		res.RetryConfigs = keySet.configs
   180  		return // Reject
   181  	}
   182  
   183  	// Compute the decryption context.
   184  	opener, err := key.setupOpener(handle.enc, suite)
   185  	if err != nil {
   186  		if err.Error() == errHPKEInvalidPublicKey {
   187  			// This occurs if the KEM algorithm used to generate handle.enc is
   188  			// not the same as the KEM algorithm of the key. One way this can
   189  			// happen is if the client sent a GREASE ECH extension with a
   190  			// config_id that happens to match a known config, but which uses a
   191  			// different KEM algorithm.
   192  			res.Status = ECHProviderReject
   193  			res.RetryConfigs = keySet.configs
   194  			return // Reject
   195  		}
   196  
   197  		res.Status = ECHProviderAbort
   198  		res.Alert = uint8(alertInternalError)
   199  		res.Error = err
   200  		return // Abort
   201  	}
   202  
   203  	// Serialize the decryption context.
   204  	res.Context, err = opener.MarshalBinary()
   205  	if err != nil {
   206  		res.Status = ECHProviderAbort
   207  		res.Alert = uint8(alertInternalError)
   208  		res.Error = err
   209  		return // Abort
   210  	}
   211  
   212  	res.Status = ECHProviderSuccess
   213  	return // Success
   214  }
   215  
   216  // EXP_ECHKey represents an ECH key and its corresponding configuration. The
   217  // encoding of an ECH Key has the format defined below (in TLS syntax). Note
   218  // that the ECH standard does not specify this format.
   219  //
   220  //	struct {
   221  //	    opaque sk<0..2^16-1>;
   222  //	    ECHConfig config<0..2^16>; // draft-ietf-tls-esni-13
   223  //	} ECHKey;
   224  type EXP_ECHKey struct {
   225  	sk     kem.PrivateKey
   226  	config ECHConfig
   227  }
   228  
   229  // EXP_UnmarshalECHKeys parses a sequence of ECH keys.
   230  func EXP_UnmarshalECHKeys(raw []byte) ([]EXP_ECHKey, error) {
   231  	var (
   232  		err                  error
   233  		key                  EXP_ECHKey
   234  		sk, config, contents cryptobyte.String
   235  	)
   236  	s := cryptobyte.String(raw)
   237  	keys := make([]EXP_ECHKey, 0)
   238  KeysLoop:
   239  	for !s.Empty() {
   240  		if !s.ReadUint16LengthPrefixed(&sk) ||
   241  			!s.ReadUint16LengthPrefixed(&config) {
   242  			return nil, errors.New("error parsing key")
   243  		}
   244  
   245  		key.config.raw = config
   246  		if !config.ReadUint16(&key.config.version) ||
   247  			!config.ReadUint16LengthPrefixed(&contents) ||
   248  			!config.Empty() {
   249  			return nil, errors.New("error parsing config")
   250  		}
   251  
   252  		if key.config.version != extensionECH {
   253  			continue KeysLoop
   254  		}
   255  		if !readConfigContents(&contents, &key.config) {
   256  			return nil, errors.New("error parsing config contents")
   257  		}
   258  
   259  		for _, suite := range key.config.suites {
   260  			if !hpke.KDF(suite.KDF).IsValid() ||
   261  				!hpke.AEAD(suite.AEAD).IsValid() {
   262  				continue KeysLoop
   263  			}
   264  		}
   265  
   266  		kem := hpke.KEM(key.config.kemId)
   267  		if !kem.IsValid() {
   268  			continue KeysLoop
   269  		}
   270  		key.config.pk, err = kem.Scheme().UnmarshalBinaryPublicKey(key.config.rawPublicKey)
   271  		if err != nil {
   272  			return nil, fmt.Errorf("error parsing public key: %s", err)
   273  		}
   274  		key.sk, err = kem.Scheme().UnmarshalBinaryPrivateKey(sk)
   275  		if err != nil {
   276  			return nil, fmt.Errorf("error parsing secret key: %s", err)
   277  		}
   278  
   279  		keys = append(keys, key)
   280  	}
   281  	return keys, nil
   282  }
   283  
   284  // setupOpener computes the HPKE context used by the server in the ECH
   285  // extension.i
   286  func (key *EXP_ECHKey) setupOpener(enc []byte, suite hpkeSymmetricCipherSuite) (hpke.Opener, error) {
   287  	if key.config.raw == nil {
   288  		panic("raw config not set")
   289  	}
   290  	hpkeSuite, err := hpkeAssembleSuite(
   291  		key.config.kemId,
   292  		suite.KDF,
   293  		suite.AEAD,
   294  	)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	info := append(append([]byte(echHpkeInfoSetup), 0), key.config.raw...)
   299  	receiver, err := hpkeSuite.NewReceiver(key.sk, info)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	return receiver.Setup(enc)
   304  }