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 }