@@ -153,7 +153,7 @@ public struct PSKServerContext: Sendable, Hashable {
153
153
public let clientIdentity : String
154
154
/// Maximum length of the returned PSK.
155
155
public let maxPSKLength : Int
156
-
156
+
157
157
/// Constructs a ``PSKServerContext``.
158
158
///
159
159
/// - parameter hint: Optional identity hint provided to the client.
@@ -172,7 +172,7 @@ public struct PSKClientContext: Sendable, Hashable {
172
172
public let hint : String ?
173
173
/// Maximum length of the returned PSK.
174
174
public let maxPSKLength : Int
175
-
175
+
176
176
/// Constructs a ``PSKClientContext``.
177
177
///
178
178
/// - parameter hint: Optional identity hint provided by the server.
@@ -213,6 +213,140 @@ public struct PSKClientIdentityResponse: Sendable {
213
213
}
214
214
}
215
215
216
+ /// A structure representing values from client extensions in the SSL/TLS handshake.
217
+ ///
218
+ /// This struct contains values obtained from the client hello message extensions during the TLS handshake process and
219
+ /// can be manipulated or introspected by the `NIOSSLContextCallback` to alter the TLS handshake behaviour dynamically
220
+ /// based on these values.
221
+ public struct NIOSSLClientExtensionValues : Hashable , Sendable {
222
+
223
+ /// The hostname value from the Server Name Indication (SNI) extension.
224
+ ///
225
+ /// This value, if available, indicates the requested server hostname by the client.
226
+ /// In a context where a service is handling multiple hostnames (virtual hosts, for example),
227
+ /// this value could be used to decide which SSLContext to use for the handshake.
228
+ public var serverHostname : String ?
229
+
230
+ /// Initializes a new `NIOSSLClientExtensionValues` struct.
231
+ ///
232
+ /// - parameter serverHostname: The hostname value from the SNI extension.
233
+ public init ( serverHostname: String ? ) {
234
+ self . serverHostname = serverHostname
235
+ }
236
+ }
237
+
238
+ /// A structure representing changes to the SSL/TLS configuration that can be applied
239
+ /// after the client hello message extensions have been processed.
240
+ public struct NIOSSLContextConfigurationOverride : Sendable {
241
+
242
+ /// The new certificate chain to use for the handshake.
243
+ public var certificateChain : [ NIOSSLCertificateSource ] ?
244
+
245
+ /// The new private key to use for the handshake.
246
+ public var privateKey : NIOSSLPrivateKeySource ?
247
+
248
+ public init ( ) { }
249
+ }
250
+
251
+ extension NIOSSLContextConfigurationOverride {
252
+
253
+ /// Return inside `NIOSSLContextCallback` when there are no changes to be made
254
+ public static let noChanges = Self ( )
255
+ }
256
+
257
+ /// A callback that can used to support multiple or dynamic TLS hosts.
258
+ ///
259
+ /// When set, this callback will be invoked once per TLS hello. The provided `NIOSSLClientExtensionValues` will contain the
260
+ /// host name indicated in the TLS client hello.
261
+ ///
262
+ /// Within this callback, the user can create and return a new `NIOSSLContextConfigurationOverride` for the given host,
263
+ /// and the delta will be applied to the current handshake configuration.
264
+ ///
265
+ public typealias NIOSSLContextCallback = @Sendable ( NIOSSLClientExtensionValues , EventLoopPromise < NIOSSLContextConfigurationOverride > ) -> Void
266
+
267
+ /// A struct that provides helpers for working with a NIOSSLContextCallback.
268
+ internal struct CustomContextManager : Sendable {
269
+ private let callback : NIOSSLContextCallback
270
+
271
+ private var state : State
272
+
273
+ init ( callback: @escaping NIOSSLContextCallback ) {
274
+ self . callback = callback
275
+ self . state = . notStarted
276
+ }
277
+ }
278
+
279
+ extension CustomContextManager {
280
+ private enum State {
281
+ case notStarted
282
+
283
+ case pendingResult
284
+
285
+ case complete( Result < NIOSSLContextConfigurationOverride , Error > )
286
+ }
287
+ }
288
+
289
+ extension CustomContextManager {
290
+ internal var loadContextError : ( any Error ) ? {
291
+ switch self . state {
292
+ case . complete( . failure( let error) ) :
293
+ return error
294
+ default :
295
+ return nil
296
+ }
297
+ }
298
+ }
299
+
300
+ extension CustomContextManager {
301
+ mutating func loadContext( ssl: OpaquePointer ) -> Result < NIOSSLContextConfigurationOverride , Error > ? {
302
+ switch state {
303
+ case . pendingResult:
304
+ // In the pending case we return nil
305
+ return nil
306
+ case . complete( let result) :
307
+ // In the complete we can return our result
308
+ return result
309
+ case . notStarted:
310
+ // Load the attached connection so we can resume handshake when future resolves
311
+ let connection = SSLConnection . loadConnectionFromSSL ( ssl)
312
+
313
+ guard let eventLoop = connection. eventLoop else {
314
+ preconditionFailure ( """
315
+ SSL_CTX_set_cert_cb was executed without an event loop assigned to the connection.
316
+ This should not be possible, please file an issue.
317
+ """ )
318
+ }
319
+
320
+ // Construct extension values to be passed to callback
321
+ let cServerHostname = CNIOBoringSSL_SSL_get_servername ( ssl, TLSEXT_NAMETYPE_host_name)
322
+ let serverHostname = cServerHostname. map { String ( cString: $0) }
323
+ let values = NIOSSLClientExtensionValues ( serverHostname: serverHostname)
324
+
325
+ // Before invoking the user callback we can update our state to pending
326
+ self . state = . pendingResult
327
+
328
+ // We're responsible for creating the promise and the user provided callback will fulfill it
329
+ let promise = eventLoop. makePromise ( of: NIOSSLContextConfigurationOverride . self)
330
+ self . callback ( values, promise)
331
+
332
+ promise. futureResult. whenComplete { result in
333
+ // Ensure we execute any completion on the next event loop tick
334
+ // This ensures that we suspend before calling resume
335
+ eventLoop. execute {
336
+ connection. parentContext. customContextManager? . state = . complete( result)
337
+ connection. parentHandler? . resumeHandshake ( )
338
+ }
339
+ }
340
+
341
+ return nil
342
+ }
343
+ }
344
+
345
+ mutating func setLoadContextError( _ error: any Error ) {
346
+ self . state = . complete( . failure( error) )
347
+ }
348
+ }
349
+
216
350
/// The callback used for providing a PSK on the client side.
217
351
///
218
352
/// The callback is invoked on the event loop with the PSK hint. This callback must complete synchronously: it cannot return a future.
0 commit comments